using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { /// /// Window implementation for Win32 platform. /// public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithNativeControlHost { private static readonly List s_instances = new List(); private static readonly IntPtr DefaultCursor = LoadCursor( IntPtr.Zero, new IntPtr((int)UnmanagedMethods.Cursor.IDC_ARROW)); private static readonly Dictionary s_edgeLookup = new Dictionary { { WindowEdge.East, HitTestValues.HTRIGHT }, { WindowEdge.North, HitTestValues.HTTOP }, { WindowEdge.NorthEast, HitTestValues.HTTOPRIGHT }, { WindowEdge.NorthWest, HitTestValues.HTTOPLEFT }, { WindowEdge.South, HitTestValues.HTBOTTOM }, { WindowEdge.SouthEast, HitTestValues.HTBOTTOMRIGHT }, { WindowEdge.SouthWest, HitTestValues.HTBOTTOMLEFT }, { WindowEdge.West, HitTestValues.HTLEFT } }; private SavedWindowInfo _savedWindowInfo; private bool _isFullScreenActive; private bool _isClientAreaExtended; private Thickness _extendedMargins; private Thickness _offScreenMargin; private double _extendTitleBarHint = -1; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE); private readonly TouchDevice _touchDevice; private readonly MouseDevice _mouseDevice; private readonly ManagedDeferredRendererLock _rendererLock; private readonly FramebufferManager _framebuffer; private readonly IGlPlatformSurface _gl; private Win32NativeControlHost _nativeControlHost; private WndProc _wndProcDelegate; private string _className; private IntPtr _hwnd; private bool _multitouch; private IInputRoot _owner; private WindowProperties _windowProperties; private bool _trackingMouse; private bool _topmost; private double _scaling = 1; private WindowState _showWindowState; private WindowState _lastWindowState; private OleDropTarget _dropTarget; private Size _minSize; private Size _maxSize; private POINT _maxTrackSize; private WindowImpl _parent; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; public WindowImpl() { _touchDevice = new TouchDevice(); _mouseDevice = new WindowsMouseDevice(); #if USE_MANAGED_DRAG _managedDrag = new ManagedWindowResizeDragHelper(this, capture => { if (capture) UnmanagedMethods.SetCapture(Handle.Handle); else UnmanagedMethods.ReleaseCapture(); }); #endif _windowProperties = new WindowProperties { ShowInTaskbar = false, IsResizable = true, Decorations = SystemDecorations.Full }; _rendererLock = new ManagedDeferredRendererLock(); CreateWindow(); _framebuffer = new FramebufferManager(_hwnd); if (Win32GlManager.EglFeature != null) _gl = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); Screen = new ScreenImpl(); _nativeControlHost = new Win32NativeControlHost(this); s_instances.Add(this); } public Action Activated { get; set; } public Func Closing { get; set; } public Action Closed { get; set; } public Action Deactivated { get; set; } public Action Input { get; set; } public Action Paint { get; set; } public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action PositionChanged { get; set; } public Action WindowStateChanged { get; set; } public Action LostFocus { get; set; } public Action TransparencyLevelChanged { get; set; } public Thickness BorderThickness { get { if (HasFullDecorations) { var style = GetStyle(); var exStyle = GetExtendedStyle(); var padding = new RECT(); if (AdjustWindowRectEx(ref padding, (uint)style, false, (uint)exStyle)) { return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); } else { throw new Win32Exception(); } } else { return new Thickness(); } } } public double RenderScaling => _scaling; public double DesktopScaling => RenderScaling; public Size ClientSize { get { GetClientRect(_hwnd, out var rect); return new Size(rect.right, rect.bottom) / RenderScaling; } } public IScreenImpl Screen { get; } public IPlatformHandle Handle { get; private set; } public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling); public IMouseDevice MouseDevice => _mouseDevice; public WindowState WindowState { get { if(_isFullScreenActive) { return WindowState.FullScreen; } var placement = default(WINDOWPLACEMENT); GetWindowPlacement(_hwnd, ref placement); return placement.ShowCmd switch { ShowWindowCommand.Maximize => WindowState.Maximized, ShowWindowCommand.Minimize => WindowState.Minimized, _ => WindowState.Normal }; } set { if (IsWindowVisible(_hwnd)) { ShowWindow(value); } else { _showWindowState = value; } } } public WindowTransparencyLevel TransparencyLevel { get; private set; } protected IntPtr Hwnd => _hwnd; public void SetTransparencyLevelHint (WindowTransparencyLevel transparencyLevel) { TransparencyLevel = EnableBlur(transparencyLevel); } private WindowTransparencyLevel EnableBlur(WindowTransparencyLevel transparencyLevel) { if (Win32Platform.WindowsVersion.Major >= 6) { if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) { return WindowTransparencyLevel.None; } else if (Win32Platform.WindowsVersion.Major >= 10) { return Win10EnableBlur(transparencyLevel); } else if (Win32Platform.WindowsVersion.Minor >= 2) { return Win8xEnableBlur(transparencyLevel); } else { return Win7EnableBlur(transparencyLevel); } } else { return WindowTransparencyLevel.None; } } private WindowTransparencyLevel Win7EnableBlur(WindowTransparencyLevel transparencyLevel) { if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) { transparencyLevel = WindowTransparencyLevel.Blur; } var blurInfo = new DWM_BLURBEHIND(false); if (transparencyLevel == WindowTransparencyLevel.Blur) { blurInfo = new DWM_BLURBEHIND(true); } DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); if (transparencyLevel == WindowTransparencyLevel.Transparent) { return WindowTransparencyLevel.None; } else { return transparencyLevel; } } private WindowTransparencyLevel Win8xEnableBlur(WindowTransparencyLevel transparencyLevel) { var accent = new AccentPolicy(); var accentStructSize = Marshal.SizeOf(accent); if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) { transparencyLevel = WindowTransparencyLevel.Blur; } if (transparencyLevel == WindowTransparencyLevel.Transparent) { accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; } else { accent.AccentState = AccentState.ACCENT_DISABLED; } var accentPtr = Marshal.AllocHGlobal(accentStructSize); Marshal.StructureToPtr(accent, accentPtr, false); var data = new WindowCompositionAttributeData(); data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; data.SizeOfData = accentStructSize; data.Data = accentPtr; SetWindowCompositionAttribute(_hwnd, ref data); Marshal.FreeHGlobal(accentPtr); if (transparencyLevel >= WindowTransparencyLevel.Blur) { Win7EnableBlur(transparencyLevel); } return transparencyLevel; } private WindowTransparencyLevel Win10EnableBlur(WindowTransparencyLevel transparencyLevel) { bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628; var accent = new AccentPolicy(); var accentStructSize = Marshal.SizeOf(accent); if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic) { transparencyLevel = WindowTransparencyLevel.Blur; } switch (transparencyLevel) { default: case WindowTransparencyLevel.None: accent.AccentState = AccentState.ACCENT_DISABLED; break; case WindowTransparencyLevel.Transparent: accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT; break; case WindowTransparencyLevel.Blur: accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; break; case WindowTransparencyLevel.AcrylicBlur: case (WindowTransparencyLevel.AcrylicBlur + 1): // hack-force acrylic. accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; transparencyLevel = WindowTransparencyLevel.AcrylicBlur; break; } accent.AccentFlags = 2; accent.GradientColor = 0x01000000; var accentPtr = Marshal.AllocHGlobal(accentStructSize); Marshal.StructureToPtr(accent, accentPtr, false); var data = new WindowCompositionAttributeData(); data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; data.SizeOfData = accentStructSize; data.Data = accentPtr; SetWindowCompositionAttribute(_hwnd, ref data); Marshal.FreeHGlobal(accentPtr); return transparencyLevel; } public IEnumerable Surfaces => new object[] { Handle, _gl, _framebuffer }; public PixelPoint Position { get { GetWindowRect(_hwnd, out var rc); return new PixelPoint(rc.left, rc.top); } set { SetWindowPos( Handle.Handle, IntPtr.Zero, value.X, value.Y, 0, 0, SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE); } } private bool HasFullDecorations => _windowProperties.Decorations == SystemDecorations.Full; public void Move(PixelPoint point) => Position = point; public void SetMinMaxSize(Size minSize, Size maxSize) { _minSize = minSize; _maxSize = maxSize; } public IRenderer CreateRenderer(IRenderRoot root) { var loop = AvaloniaLocator.Current.GetService(); var customRendererFactory = AvaloniaLocator.Current.GetService(); if (customRendererFactory != null) return customRendererFactory.Create(root, loop); return Win32Platform.UseDeferredRendering ? (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock) : new ImmediateRenderer(root); } public void Resize(Size value) { int requestedClientWidth = (int)(value.Width * RenderScaling); int requestedClientHeight = (int)(value.Height * RenderScaling); GetClientRect(_hwnd, out var clientRect); // do comparison after scaling to avoid rounding issues if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height) { GetWindowRect(_hwnd, out var windowRect); SetWindowPos( _hwnd, IntPtr.Zero, 0, 0, requestedClientWidth + (windowRect.Width - clientRect.Width), requestedClientHeight + (windowRect.Height - clientRect.Height), SetWindowPosFlags.SWP_RESIZE); } } public void Activate() { SetActiveWindow(_hwnd); } public IPopupImpl CreatePopup() => Win32Platform.UseOverlayPopups ? null : new PopupImpl(this); public void Dispose() { if (_dropTarget != null) { OleContext.Current?.UnregisterDragDrop(Handle); _dropTarget = null; } if (_hwnd != IntPtr.Zero) { DestroyWindow(_hwnd); _hwnd = IntPtr.Zero; } if (_className != null) { UnregisterClass(_className, GetModuleHandle(null)); _className = null; } _framebuffer.Dispose(); } public void Invalidate(Rect rect) { var scaling = RenderScaling; var r = new RECT { left = (int)Math.Floor(rect.X * scaling), top = (int)Math.Floor(rect.Y * scaling), right = (int)Math.Ceiling(rect.Right * scaling), bottom = (int)Math.Ceiling(rect.Bottom * scaling), }; InvalidateRect(_hwnd, ref r, false); } public Point PointToClient(PixelPoint point) { var p = new POINT { X = point.X, Y = point.Y }; UnmanagedMethods.ScreenToClient(_hwnd, ref p); return new Point(p.X, p.Y) / RenderScaling; } public PixelPoint PointToScreen(Point point) { point *= RenderScaling; var p = new POINT { X = (int)point.X, Y = (int)point.Y }; ClientToScreen(_hwnd, ref p); return new PixelPoint(p.X, p.Y); } public void SetInputRoot(IInputRoot inputRoot) { _owner = inputRoot; CreateDropTarget(); } public void Hide() { UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide); } public virtual void Show() { SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent != null ? _parent._hwnd : IntPtr.Zero); ShowWindow(_showWindowState); } public Action GotInputWhenDisabled { get; set; } public void SetParent(IWindowImpl parent) { _parent = (WindowImpl)parent; SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent._hwnd); } public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable); public void BeginMoveDrag(PointerPressedEventArgs e) { _mouseDevice.Capture(null); DefWindowProc(_hwnd, (int)WindowsMessage.WM_NCLBUTTONDOWN, new IntPtr((int)HitTestValues.HTCAPTION), IntPtr.Zero); e.Pointer.Capture(null); } public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { #if USE_MANAGED_DRAG _managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position.ToPoint(_scaling))); #else _mouseDevice.Capture(null); DefWindowProc(_hwnd, (int)WindowsMessage.WM_NCLBUTTONDOWN, new IntPtr((int)s_edgeLookup[edge]), IntPtr.Zero); #endif } public void SetTitle(string title) { SetWindowText(_hwnd, title); } public void SetCursor(IPlatformHandle cursor) { var hCursor = cursor?.Handle ?? DefaultCursor; SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor); if (_owner.IsPointerOver) { UnmanagedMethods.SetCursor(hCursor); } } public void SetIcon(IWindowIconImpl icon) { var impl = (IconImpl)icon; var hIcon = impl?.HIcon ?? IntPtr.Zero; PostMessage(_hwnd, (int)WindowsMessage.WM_SETICON, new IntPtr((int)Icons.ICON_BIG), hIcon); } public void ShowTaskbarIcon(bool value) { var newWindowProperties = _windowProperties; newWindowProperties.ShowInTaskbar = value; UpdateWindowProperties(newWindowProperties); } public void CanResize(bool value) { var newWindowProperties = _windowProperties; newWindowProperties.IsResizable = value; UpdateWindowProperties(newWindowProperties); } public void SetSystemDecorations(SystemDecorations value) { var newWindowProperties = _windowProperties; newWindowProperties.Decorations = value; UpdateWindowProperties(newWindowProperties); } public void SetTopmost(bool value) { if (value == _topmost) { return; } IntPtr hWndInsertAfter = value ? WindowPosZOrder.HWND_TOPMOST : WindowPosZOrder.HWND_NOTOPMOST; SetWindowPos(_hwnd, hWndInsertAfter, 0, 0, 0, 0, SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE); _topmost = value; } protected virtual IntPtr CreateWindowOverride(ushort atom) { return CreateWindowEx( 0, atom, null, (int)WindowStyles.WS_OVERLAPPEDWINDOW | (int) WindowStyles.WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); } private void CreateWindow() { // Ensure that the delegate doesn't get garbage collected by storing it as a field. _wndProcDelegate = WndProc; _className = $"Avalonia-{Guid.NewGuid().ToString()}"; // Unique DC helps with performance when using Gpu based rendering const ClassStyles windowClassStyle = ClassStyles.CS_OWNDC | ClassStyles.CS_HREDRAW | ClassStyles.CS_VREDRAW; var wndClassEx = new WNDCLASSEX { cbSize = Marshal.SizeOf(), style = (int)windowClassStyle, lpfnWndProc = _wndProcDelegate, hInstance = GetModuleHandle(null), hCursor = DefaultCursor, hbrBackground = IntPtr.Zero, lpszClassName = _className }; ushort atom = RegisterClassEx(ref wndClassEx); if (atom == 0) { throw new Win32Exception(); } _hwnd = CreateWindowOverride(atom); if (_hwnd == IntPtr.Zero) { throw new Win32Exception(); } Handle = new PlatformHandle(_hwnd, PlatformConstants.WindowHandleType); _multitouch = Win32Platform.Options.EnableMultitouch ?? true; if (_multitouch) { RegisterTouchWindow(_hwnd, 0); } if (ShCoreAvailable) { var monitor = MonitorFromWindow( _hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); if (GetDpiForMonitor( monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpix, out var dpiy) == 0) { _scaling = dpix / 96.0; } } } private void CreateDropTarget() { var odt = new OleDropTarget(this, _owner); if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false) { _dropTarget = odt; } } /// /// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc /// Method must only be called from inside UpdateWindowProperties. /// /// private void SetFullScreen(bool fullscreen) { if (fullscreen) { GetWindowRect(_hwnd, out var windowRect); _savedWindowInfo.WindowRect = windowRect; var current = GetStyle(); var currentEx = GetExtendedStyle(); _savedWindowInfo.Style = current; _savedWindowInfo.ExStyle = currentEx; // Set new window style and size. SetStyle(current & ~(WindowStyles.WS_CAPTION | WindowStyles.WS_THICKFRAME), false); SetExtendedStyle(currentEx & ~(WindowStyles.WS_EX_DLGMODALFRAME | WindowStyles.WS_EX_WINDOWEDGE | WindowStyles.WS_EX_CLIENTEDGE | WindowStyles.WS_EX_STATICEDGE), false); // On expand, if we're given a window_rect, grow to it, otherwise do // not resize. MONITORINFO monitor_info = MONITORINFO.Create(); GetMonitorInfo(MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST), ref monitor_info); var window_rect = monitor_info.rcMonitor.ToPixelRect(); SetWindowPos(_hwnd, IntPtr.Zero, window_rect.X, window_rect.Y, window_rect.Width, window_rect.Height, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); _isFullScreenActive = true; } else { // Reset original window style and size. The multiple window size/moves // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be // repainted. Better-looking methods welcome. _isFullScreenActive = false; var windowStates = GetWindowStateStyles(); SetStyle((_savedWindowInfo.Style & ~WindowStateMask) | windowStates, false); SetExtendedStyle(_savedWindowInfo.ExStyle, false); // On restore, resize to the previous saved rect size. var new_rect = _savedWindowInfo.WindowRect.ToPixelRect(); SetWindowPos(_hwnd, IntPtr.Zero, new_rect.X, new_rect.Y, new_rect.Width, new_rect.Height, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); UpdateWindowProperties(_windowProperties, true); } TaskBarList.MarkFullscreen(_hwnd, fullscreen); ExtendClientArea(); } private MARGINS UpdateExtendMargins() { RECT borderThickness = new RECT(); RECT borderCaptionThickness = new RECT(); AdjustWindowRectEx(ref borderCaptionThickness, (uint)(GetStyle()), false, 0); AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle() & ~WindowStyles.WS_CAPTION), false, 0); borderThickness.left *= -1; borderThickness.top *= -1; borderCaptionThickness.left *= -1; borderCaptionThickness.top *= -1; bool wantsTitleBar = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1; if (!wantsTitleBar) { borderCaptionThickness.top = 1; } MARGINS margins = new MARGINS(); margins.cxLeftWidth = 1; margins.cxRightWidth = 1; margins.cyBottomHeight = 1; if (_extendTitleBarHint != -1) { borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling); } margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1; if (WindowState == WindowState.Maximized) { _extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / RenderScaling, 0, 0); _offScreenMargin = new Thickness(borderThickness.left / RenderScaling, borderThickness.top / RenderScaling, borderThickness.right / RenderScaling, borderThickness.bottom / RenderScaling); } else { _extendedMargins = new Thickness(0, (borderCaptionThickness.top) / RenderScaling, 0, 0); _offScreenMargin = new Thickness(); } return margins; } private void ExtendClientArea() { if (DwmIsCompositionEnabled(out bool compositionEnabled) < 0 || !compositionEnabled) { _isClientAreaExtended = false; return; } GetWindowRect(_hwnd, out var rcClient); // Inform the application of the frame change. SetWindowPos(_hwnd, IntPtr.Zero, rcClient.left, rcClient.top, rcClient.Width, rcClient.Height, SetWindowPosFlags.SWP_FRAMECHANGED); if (_isClientAreaExtended && WindowState != WindowState.FullScreen) { var margins = UpdateExtendMargins(); DwmExtendFrameIntoClientArea(_hwnd, ref margins); } else { var margins = new MARGINS(); DwmExtendFrameIntoClientArea(_hwnd, ref margins); _offScreenMargin = new Thickness(); _extendedMargins = new Thickness(); } if(!_isClientAreaExtended || (_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome))) { EnableCloseButton(_hwnd); } else { DisableCloseButton(_hwnd); } ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended); } private void ShowWindow(WindowState state) { ShowWindowCommand? command; var newWindowProperties = _windowProperties; switch (state) { case WindowState.Minimized: newWindowProperties.IsFullScreen = false; command = ShowWindowCommand.Minimize; break; case WindowState.Maximized: newWindowProperties.IsFullScreen = false; command = ShowWindowCommand.Maximize; break; case WindowState.Normal: newWindowProperties.IsFullScreen = false; command = ShowWindowCommand.Restore; break; case WindowState.FullScreen: newWindowProperties.IsFullScreen = true; command = IsWindowVisible(_hwnd) ? (ShowWindowCommand?)null : ShowWindowCommand.Restore; break; default: throw new ArgumentException("Invalid WindowState."); } UpdateWindowProperties(newWindowProperties); if (command.HasValue) { UnmanagedMethods.ShowWindow(_hwnd, command.Value); } if (state == WindowState.Maximized) { MaximizeWithoutCoveringTaskbar(); } if (!Design.IsDesignMode) { SetFocus(_hwnd); } } private void MaximizeWithoutCoveringTaskbar() { IntPtr monitor = MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); if (monitor != IntPtr.Zero) { var monitorInfo = MONITORINFO.Create(); if (GetMonitorInfo(monitor, ref monitorInfo)) { 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, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW); } } } private WindowStyles GetWindowStateStyles() { return GetStyle() & WindowStateMask; } private WindowStyles GetStyle() { if (_isFullScreenActive) { return _savedWindowInfo.Style; } else { return (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); } } private WindowStyles GetExtendedStyle() { if (_isFullScreenActive) { return _savedWindowInfo.ExStyle; } else { return (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE); } } private void SetStyle(WindowStyles style, bool save = true) { if (save) { _savedWindowInfo.Style = style; } if (!_isFullScreenActive) { SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); } } private void SetExtendedStyle(WindowStyles style, bool save = true) { if (save) { _savedWindowInfo.ExStyle = style; } if (!_isFullScreenActive) { SetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE, (uint)style); } } private void UpdateWindowProperties(WindowProperties newProperties, bool forceChanges = false) { var oldProperties = _windowProperties; // Calling SetWindowPos will cause events to be sent and we need to respond // according to the new values already. _windowProperties = newProperties; if ((oldProperties.ShowInTaskbar != newProperties.ShowInTaskbar) || forceChanges) { var exStyle = GetExtendedStyle(); if (newProperties.ShowInTaskbar) { exStyle |= WindowStyles.WS_EX_APPWINDOW; } else { exStyle &= ~WindowStyles.WS_EX_APPWINDOW; } SetExtendedStyle(exStyle); // TODO: To hide non-owned window from taskbar we need to parent it to a hidden window. // Otherwise it will still show in the taskbar. } WindowStyles style; if ((oldProperties.IsResizable != newProperties.IsResizable) || forceChanges) { style = GetStyle(); if (newProperties.IsResizable) { style |= WindowStyles.WS_SIZEFRAME; style |= WindowStyles.WS_MAXIMIZEBOX; } else { style &= ~WindowStyles.WS_SIZEFRAME; style &= ~WindowStyles.WS_MAXIMIZEBOX; } SetStyle(style); } if (oldProperties.IsFullScreen != newProperties.IsFullScreen) { SetFullScreen(newProperties.IsFullScreen); } if ((oldProperties.Decorations != newProperties.Decorations) || forceChanges) { style = GetStyle(); const WindowStyles fullDecorationFlags = WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU; if (newProperties.Decorations == SystemDecorations.Full) { style |= fullDecorationFlags; } else { style &= ~fullDecorationFlags; } SetStyle(style); if (!_isFullScreenActive) { var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0; var margins = new MARGINS { cyBottomHeight = margin, cxRightWidth = margin, cxLeftWidth = margin, cyTopHeight = margin }; DwmExtendFrameIntoClientArea(_hwnd, ref margins); GetClientRect(_hwnd, out var oldClientRect); var oldClientRectOrigin = new POINT(); ClientToScreen(_hwnd, ref oldClientRectOrigin); oldClientRect.Offset(oldClientRectOrigin); var newRect = oldClientRect; if (newProperties.Decorations == SystemDecorations.Full) { AdjustWindowRectEx(ref newRect, (uint)style, false, (uint)GetExtendedStyle()); } SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); } } } private const int MF_BYCOMMAND = 0x0; private const int MF_BYPOSITION = 0x400; private const int MF_REMOVE = 0x1000; private const int MF_ENABLED = 0x0; private const int MF_GRAYED = 0x1; private const int MF_DISABLED = 0x2; private const int SC_CLOSE = 0xF060; void DisableCloseButton(IntPtr hwnd) { EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); } void EnableCloseButton(IntPtr hwnd) { EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE, MF_BYCOMMAND | MF_ENABLED); } #if USE_MANAGED_DRAG private Point ScreenToClient(Point point) { var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y }; UnmanagedMethods.ScreenToClient(_hwnd, ref p); return new Point(p.X, p.Y); } #endif PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size { get { GetClientRect(_hwnd, out var rect); return new PixelSize( Math.Max(1, rect.right - rect.left), Math.Max(1, rect.bottom - rect.top)); } } double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => RenderScaling; IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; public void SetExtendClientAreaToDecorationsHint(bool hint) { _isClientAreaExtended = hint; ExtendClientArea(); } public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) { _extendChromeHints = hints; ExtendClientArea(); } /// public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) { _extendTitleBarHint = titleBarHeight; ExtendClientArea(); } /// public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; /// public Action ExtendClientAreaToDecorationsChanged { get; set; } /// public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome); /// public Thickness ExtendedMargins => _extendedMargins; /// public Thickness OffScreenMargin => _offScreenMargin; /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); private struct SavedWindowInfo { public WindowStyles Style { get; set; } public WindowStyles ExStyle { get; set; } public RECT WindowRect { get; set; } }; private struct WindowProperties { public bool ShowInTaskbar; public bool IsResizable; public SystemDecorations Decorations; public bool IsFullScreen; } } }