diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index c5e3622404..0e1540b5fd 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -107,6 +107,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform public void SetSystemDecorations(bool enabled) { } + + public void SetCoverTaskbarWhenMaximized(bool enable) + { + //Not supported + } + public void Invalidate(Rect rect) { if (Holder?.Surface?.IsValid == true) base.Invalidate(); diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 609e9834cb..fd2feb539f 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -35,6 +35,11 @@ namespace Avalonia.Platform /// void SetSystemDecorations(bool enabled); + /// + /// When system decorations are disabled sets if the maximized state covers the entire screen or just the working area. + /// + void SetCoverTaskbarWhenMaximized(bool enable); + /// /// Sets the icon of this window. /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index e0d5cddc9e..6755dee073 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -64,6 +64,13 @@ namespace Avalonia.Controls public static readonly StyledProperty HasSystemDecorationsProperty = AvaloniaProperty.Register(nameof(HasSystemDecorations), true); + /// + /// Sets if the window should cover the taskbar when maximized. Only applies to Windows + /// with HasSystemDecorations = false. + /// + public static readonly StyledProperty CoverTaskbarOnMaximizeProperty = + AvaloniaProperty.Register(nameof(CoverTaskbarOnMaximize), true); + /// /// Defines the property. /// @@ -89,6 +96,10 @@ namespace Avalonia.Controls TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue)); HasSystemDecorationsProperty.Changed.AddClassHandler( (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue)); + + CoverTaskbarOnMaximizeProperty.Changed.AddClassHandler( + (s, e) => s.PlatformImpl.SetCoverTaskbarWhenMaximized((bool)e.NewValue)); + IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); } @@ -157,6 +168,16 @@ namespace Avalonia.Controls set { SetValue(HasSystemDecorationsProperty, value); } } + /// + /// Sets if the window should cover the taskbar when maximized. Only applies to Windows + /// with HasSystemDecorations = false. + /// + public bool CoverTaskbarOnMaximize + { + get { return GetValue(CoverTaskbarOnMaximizeProperty); } + set { SetValue(CoverTaskbarOnMaximizeProperty, value); } + } + /// /// Gets or sets the minimized/maximized state of the window. /// diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index a9ecfa4058..8641f2f431 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -304,6 +304,11 @@ namespace Avalonia.Gtk args.RetVal = true; } + public void SetCoverTaskbarWhenMaximized(bool enable) + { + // No action neccesary on Gtk. + } + public void Dispose() { _window.Hide(); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index d115b35a23..c2785095d0 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -850,6 +850,10 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR dwFlags); + + [DllImport("user32", EntryPoint = "GetMonitorInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetMonitorInfo([In] IntPtr hMonitor, [Out] MONITORINFO lpmi); [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] @@ -862,6 +866,23 @@ namespace Avalonia.Win32.Interop MONITOR_DEFAULTTONEAREST = 0x00000002, } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal class MONITORINFO + { + public int cbSize = Marshal.SizeOf(typeof(MONITORINFO)); + public RECT rcMonitor = new RECT(); + public RECT rcWork = new RECT(); + public int dwFlags = 0; + + public enum MonitorOptions : uint + { + MONITOR_DEFAULTTONULL = 0x00000000, + MONITOR_DEFAULTTOPRIMARY = 0x00000001, + MONITOR_DEFAULTTONEAREST = 0x00000002 + } + } + + public enum PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE = 0, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5db3f69f85..2129090a64 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -15,6 +15,7 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; +using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { @@ -32,6 +33,7 @@ namespace Avalonia.Win32 private bool _trackingMouse; private bool _isActive; private bool _decorated = true; + private bool _coverTaskBarWhenMaximized = true; private double _scaling = 1; private WindowState _showWindowState; @@ -135,7 +137,7 @@ namespace Avalonia.Win32 { var placement = default(UnmanagedMethods.WINDOWPLACEMENT); UnmanagedMethods.GetWindowPlacement(_hwnd, ref placement); - + switch (placement.ShowCmd) { case UnmanagedMethods.ShowWindowCommand.Maximize: @@ -184,22 +186,32 @@ namespace Avalonia.Win32 public void SetSystemDecorations(bool value) { if (value == _decorated) + { return; - var style = (UnmanagedMethods.WindowStyles) UnmanagedMethods.GetWindowLong(_hwnd, -16); + } + + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); + style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + if (!value) + { style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + } UnmanagedMethods.RECT windowRect; UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); + Rect newRect; var oldThickness = BorderThickness; - UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint) style); + UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style); + if (value) { var thickness = BorderThickness; + newRect = new Rect( windowRect.left - thickness.Left, windowRect.top - thickness.Top, @@ -207,19 +219,19 @@ namespace Avalonia.Win32 (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); } else + { newRect = new Rect( windowRect.left + oldThickness.Left, windowRect.top + oldThickness.Top, (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int) newRect.X, (int) newRect.Y, (int) newRect.Width, - (int) newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); + } + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); _decorated = value; - - } public void Invalidate(Rect rect) @@ -268,7 +280,7 @@ namespace Avalonia.Win32 public void BeginMoveDrag() { - UnmanagedMethods.DefWindowProc(_hwnd, (int) UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, + UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, new IntPtr((int)UnmanagedMethods.HitTestValues.HTCAPTION), IntPtr.Zero); } @@ -286,8 +298,8 @@ namespace Avalonia.Win32 public void BeginResizeDrag(WindowEdge edge) { - UnmanagedMethods.DefWindowProc(_hwnd, (int) UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, - new IntPtr((int) EdgeDic[edge]), IntPtr.Zero); + UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, + new IntPtr((int)EdgeDic[edge]), IntPtr.Zero); } public Point Position @@ -303,8 +315,8 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPos( Handle.Handle, IntPtr.Zero, - (int) value.X, - (int) value.Y, + (int)value.X, + (int)value.Y, 0, 0, UnmanagedMethods.SetWindowPosFlags.SWP_NOSIZE | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); @@ -454,7 +466,7 @@ namespace Avalonia.Win32 : RawMouseEventType.MiddleButtonDown, DipFromLParam(lParam), GetMouseModifiers(wParam)); break; - + case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP: @@ -462,9 +474,9 @@ namespace Avalonia.Win32 WindowsMouseDevice.Instance, timestamp, _owner, - msg == (int) UnmanagedMethods.WindowsMessage.WM_LBUTTONUP + msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP ? RawMouseEventType.LeftButtonUp - : msg == (int) UnmanagedMethods.WindowsMessage.WM_RBUTTONUP + : msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONUP ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp, DipFromLParam(lParam), GetMouseModifiers(wParam)); @@ -508,7 +520,7 @@ namespace Avalonia.Win32 timestamp, _owner, ScreenToClient(DipFromLParam(lParam)), - new Vector(-(ToInt32(wParam) >> 16) / wheelDelta,0), GetMouseModifiers(wParam)); + new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam)); break; case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE: @@ -585,9 +597,9 @@ namespace Avalonia.Win32 var modifiers = WindowsKeyboardDevice.Instance.Modifiers; if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_LBUTTON)) modifiers |= InputModifiers.LeftMouseButton; - if(keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_RBUTTON)) - modifiers |= InputModifiers.RightMouseButton; - if(keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_MBUTTON)) + if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_RBUTTON)) + modifiers |= InputModifiers.RightMouseButton; + if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_MBUTTON)) modifiers |= InputModifiers.MiddleMouseButton; return modifiers; } @@ -666,23 +678,60 @@ namespace Avalonia.Win32 { UnmanagedMethods.ShowWindowCommand command; + bool maximizeFillsDesktop = false; // otherwise we cover entire screen. + switch (state) { case WindowState.Minimized: - command = UnmanagedMethods.ShowWindowCommand.Minimize; + command = ShowWindowCommand.Minimize; break; case WindowState.Maximized: - command = UnmanagedMethods.ShowWindowCommand.Maximize; + command = ShowWindowCommand.Maximize; + + if (!_decorated && !_coverTaskBarWhenMaximized) + { + maximizeFillsDesktop = true; + } break; + case WindowState.Normal: - command = UnmanagedMethods.ShowWindowCommand.Restore; + command = ShowWindowCommand.Restore; break; + default: throw new ArgumentException("Invalid WindowState."); } UnmanagedMethods.ShowWindow(_hwnd, command); - UnmanagedMethods.SetFocus(_hwnd); + + if (maximizeFillsDesktop) + { + MaximizeWithoutCoveringTaskbar(); + } + + SetFocus(_hwnd); + } + + private void MaximizeWithoutCoveringTaskbar() + { + IntPtr monitor = MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); + + if (monitor != IntPtr.Zero) + { + MONITORINFO monitorInfo = new MONITORINFO(); + + if (GetMonitorInfo(monitor, monitorInfo)) + { + RECT rcMonitorArea = monitorInfo.rcMonitor; + + var x = monitorInfo.rcWork.left; + var y = monitorInfo.rcWork.top; + var cx = Math.Abs(monitorInfo.rcWork.right - x); + var cy = Math.Abs(monitorInfo.rcWork.bottom - y); + + SetWindowPos(_hwnd, new IntPtr(-2), x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW); + } + } } public void SetIcon(IWindowIconImpl icon) @@ -699,5 +748,15 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } + + public void SetCoverTaskbarWhenMaximized(bool enable) + { + _coverTaskBarWhenMaximized = enable; + + if (_showWindowState == WindowState.Maximized) + { + ShowWindow(WindowState.Maximized); + } + } } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 8b14d09573..67817ef62a 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -170,6 +170,11 @@ namespace Avalonia.iOS //Not supported } + public void SetCoverTaskbarWhenMaximized(bool enable) + { + //Not supported + } + public override void TouchesEnded(NSSet touches, UIEvent evt) { var touch = touches.AnyObject as UITouch;