From bd9a23a6e3c4e15725b98ffb700d4465c38aaa26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Korczy=C5=84ski?= Date: Mon, 11 Mar 2024 00:08:27 +0000 Subject: [PATCH] feat: Add API for fetching window Z-order --- native/Avalonia.Native/src/OSX/WindowImpl.h | 2 + native/Avalonia.Native/src/OSX/WindowImpl.mm | 12 ++++++ src/Avalonia.Controls/Platform/IWindowImpl.cs | 6 +++ src/Avalonia.Controls/Window.cs | 6 +++ .../Remote/PreviewerWindowImpl.cs | 1 + src/Avalonia.DesignerSupport/Remote/Stubs.cs | 1 + src/Avalonia.Native/WindowImpl.cs | 9 +++++ src/Avalonia.Native/avn.idl | 2 + src/Avalonia.X11/X11Window.cs | 40 +++++++++++++++++++ .../Avalonia.Headless/HeadlessWindowImpl.cs | 1 + .../Interop/UnmanagedMethods.cs | 14 +++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 23 +++++++++++ 12 files changed, 117 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 049ef755ffec..047a0d2c84db 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -82,6 +82,8 @@ BEGIN_INTERFACE_MAP() virtual HRESULT GetExtendTitleBarHeight (double*ret) override; virtual HRESULT SetExtendTitleBarHeight (double value) override; + + virtual HRESULT GetWindowZOrder (long* zOrder) override; void EnterFullScreenMode (); diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index be761e0af7b5..33ddaf379591 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -366,6 +366,18 @@ } } +HRESULT WindowImpl::GetWindowZOrder(long* zOrder) { + START_COM_CALL; + @autoreleasepool { + if (zOrder == nullptr) { + return E_POINTER; + } + + *zOrder = [Window orderedIndex]; + return S_OK; + } +} + HRESULT WindowImpl::TakeFocusFromChildren() { START_COM_CALL; diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index a66e5e138f8e..83d15424c2e5 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -98,6 +98,12 @@ public interface IWindowImpl : IWindowBaseImpl /// Thickness OffScreenMargin { get; } + /// + /// Gets the window's z-order position. The highest value is the front, lower numbers are further back. + /// If the z-order can't be determined or the platform doesn't support window stacking, null is returned. + /// + IntPtr? ZOrder { get; } + /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler. /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index b4abca551064..053ef8a42a71 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -427,6 +427,12 @@ public PixelPoint Position set => PlatformImpl?.Move(value); } + /// + /// Gets the window's z-order position. The highest value is the front, lower numbers are further back. + /// If the z-order can't be determined or the platform doesn't support window stacking, null is returned. + /// + public IntPtr? WindowZOrder => PlatformImpl?.ZOrder; + /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 9463224b9984..f6f15fba9445 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -39,6 +39,7 @@ public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) public double DesktopScaling => 1.0; public PixelPoint Position { get; set; } + public IntPtr? ZOrder => null; public Action PositionChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index cc183135baf0..7dcaecbb3d8b 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -41,6 +41,7 @@ class WindowStub : IWindowImpl, IPopupImpl public IPopupImpl CreatePopup() => new WindowStub(this); public PixelPoint Position { get; set; } + public IntPtr? ZOrder => null; public Action PositionChanged { get; set; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 11b264bfdd70..6bbbba7eb984 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -103,6 +103,15 @@ public WindowState WindowState public Thickness OffScreenMargin { get; } = new Thickness(); + public IntPtr? ZOrder + { + get + { + // macOS returns z-order in reverse order - the topmost window has the lowest z-order + return new IntPtr(long.MaxValue - _native.WindowZOrder.ToInt64()); + } + } + private bool _isExtended; public bool IsClientAreaExtendedToDecorations => _isExtended; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 87b95953bfa4..b0f4ba09cb87 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -2,6 +2,7 @@ @clr-access internal @clr-map bool int @clr-map u_int64_t ulong +@clr-map long IntPtr @cpp-preamble @@ #pragma once #include "com.h" @@ -750,6 +751,7 @@ interface IAvnWindow : IAvnWindowBase HRESULT SetExtendClientAreaHints(AvnExtendClientAreaChromeHints hints); HRESULT GetExtendTitleBarHeight(double*ret); HRESULT SetExtendTitleBarHeight(double value); + HRESULT GetWindowZOrder(long*ret); } [uuid(939b6599-40a8-4710-a4c8-5d72d8f174fb)] diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index cf1f8c579035..1d8e3f2da47e 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1133,6 +1133,46 @@ public PixelPoint Position } } + public IntPtr? ZOrder + { + get + { + var stack = new Stack(); + var zOrder = 0; + stack.Push(_x11.RootWindow); + + while (stack.Count > 0) + { + var currentWindow = stack.Pop(); + + if (currentWindow == _handle) + { + return new IntPtr(zOrder); + } + + zOrder++; + + if (XQueryTree(_x11.Display, currentWindow, out _, out _, + out var childrenPtr, out var childrenCount) != 0) + { + if (childrenPtr == IntPtr.Zero) + continue; + + var children = (IntPtr*)childrenPtr; + + // Children are returned bottom to top, so we need to push them in reverse order + // In order to traverse bottom children first + for (int i = childrenCount - 1; i >= 0; i--) + stack.Push(children[i]); + + XFree(childrenPtr); + } + } + + return null; + } + } + public IMouseDevice MouseDevice => _mouse; public TouchDevice TouchDevice => _touch; diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 5c2e32038319..00c1106b78aa 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -100,6 +100,7 @@ public void BeginResizeDrag(WindowEdge edge) public PixelPoint Position { get; set; } public Action? PositionChanged { get; set; } + public IntPtr? ZOrder => null; public void Activate() { Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 89ae3c5db798..5c77b401895f 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1274,6 +1274,20 @@ public static extern IntPtr CreateWindowEx( [DllImport("user32.dll")] public static extern int GetSystemMetrics(SystemMetric smIndex); + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr GetWindow(IntPtr hWnd, GetWindowCommand uCmd); + + public enum GetWindowCommand : uint + { + GW_CHILD = 5, + GW_ENABLEDPOPUP = 6, + GW_HWNDFIRST = 0, + GW_HWNDLAST = 1, + GW_HWNDNEXT = 2, + GW_HWNDPREV = 3, + GW_OWNER = 4 + } + [DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW", ExactSpelling = true)] public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index eea8d10b50a4..9208599295ab 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -711,6 +711,29 @@ public void SetParent(IWindowImpl? parent) public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable); + public IntPtr? ZOrder + { + get + { + var lowestHwnd = GetWindow(_hwnd, GetWindowCommand.GW_HWNDLAST); + + var zOrder = 0; + var hwnd = lowestHwnd; + while (hwnd != IntPtr.Zero) + { + if (_hwnd == hwnd) + { + return new IntPtr(zOrder); + } + + hwnd = GetWindow(hwnd, GetWindowCommand.GW_HWNDPREV); + zOrder++; + } + + return null; + } + } + public void BeginMoveDrag(PointerPressedEventArgs e) { e.Pointer.Capture(null);