Browse Source

feat: Add API for fetching window Z-order (#14909)

* feat: Add API for fetching window Z-order

* Addressed PR comments

* Improve X11Window::ZOrder implementation to avoid traversing windows that are not required to compute z-order

* Move zOrder API from Window to IWindowingPlatform

* Revert "Addressed PR comments"

This reverts commit 691541adf6.

* Rename

* Missing methods

* Move GetWindowsZOrder from IWindowingPlatform to IWindowImpl

* Cleanup

* Move SortWindowsByZOrder to Window class as a static method

* Implement zOrder for HeadlessWindowImpl

---------

Co-authored-by: Max Katz <maxkatz6@outlook.com>
release/11.1.0-beta2
Bartosz Korczyński 2 years ago
committed by Max Katz
parent
commit
213c17b7b7
  1. 2
      native/Avalonia.Native/src/OSX/WindowImpl.h
  2. 14
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  3. 11
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  4. 23
      src/Avalonia.Controls/Window.cs
  5. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  6. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  7. 10
      src/Avalonia.Native/WindowImpl.cs
  8. 2
      src/Avalonia.Native/avn.idl
  9. 74
      src/Avalonia.X11/X11Window.cs
  10. 16
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  11. 5
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  12. 34
      src/Windows/Avalonia.Win32/WindowImpl.cs

2
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 ();

14
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -366,6 +366,20 @@ HRESULT WindowImpl::GetWindowState(AvnWindowState *ret) {
}
}
HRESULT WindowImpl::GetWindowZOrder(long* zOrder) {
START_COM_CALL;
@autoreleasepool {
if (zOrder == nullptr) {
return E_POINTER;
}
// negate the value to match expected z-order in Avalonia
// (top-most window should have the highest z-order value)
*zOrder = -[Window orderedIndex];
return S_OK;
}
}
HRESULT WindowImpl::TakeFocusFromChildren() {
START_COM_CALL;

11
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -143,6 +143,15 @@ namespace Avalonia.Platform
/// Sets how big the non-client titlebar area should be.
/// </summary>
/// <param name="titleBarHeight">-1 for platform default, otherwise the height in DIPs.</param>
void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight);
void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight);
/// <summary>
/// Fills zOrder with numbers that represent the relative order of the windows in the z-order.
/// The topmost window should have the highest number.
/// Both the windows and zOrder lists are expected to be the same length.
/// </summary>
/// <param name="windows">A span of windows to get their z-order</param>
/// <param name="zOrder">Span to be filled with associated window z-order</param>
internal void GetWindowsZOrder(Span<Window> windows, Span<long> zOrder);
}
}

23
src/Avalonia.Controls/Window.cs

@ -842,6 +842,29 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Sorts the windows ascending by their Z order - the topmost window will be the last in the list.
/// </summary>
/// <param name="windows"></param>
public static void SortWindowsByZOrder(Window[] windows)
{
if (windows.Length == 0)
return;
if (windows[0].PlatformImpl is not { } platformImpl)
throw new InvalidOperationException("Window.PlatformImpl is null");
#if NET5_0_OR_GREATER
Span<long> zOrder = stackalloc long[windows.Length];
platformImpl.GetWindowsZOrder(windows, zOrder);
zOrder.Sort(windows.AsSpan());
#else
long[] zOrder = new long[windows.Length];
platformImpl.GetWindowsZOrder(windows, zOrder);
Array.Sort(zOrder, windows);
#endif
}
private void UpdateEnabled()
{
bool isEnabled = true;

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -153,5 +153,7 @@ namespace Avalonia.DesignerSupport.Remote
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
}
public void GetWindowsZOrder(Span<Window> windows, Span<long> zOrder) => throw new NotSupportedException();
}
}

2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -178,6 +178,8 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void GetWindowsZOrder(Span<Window> windows, Span<long> zOrder) => throw new NotSupportedException();
public IPopupPositioner PopupPositioner { get; }
public Action GotInputWhenDisabled { get; set; }

10
src/Avalonia.Native/WindowImpl.cs

@ -103,6 +103,8 @@ namespace Avalonia.Native
public Thickness OffScreenMargin { get; } = new Thickness();
public IntPtr? ZOrder => _native.WindowZOrder;
private bool _isExtended;
public bool IsClientAreaExtendedToDecorations => _isExtended;
@ -237,5 +239,13 @@ namespace Avalonia.Native
return base.TryGetFeature(featureType);
}
public void GetWindowsZOrder(Span<Window> windows, Span<long> zOrder)
{
for (int i = 0; i < windows.Length; i++)
{
zOrder[i] = (windows[i].PlatformImpl as WindowImpl)?.ZOrder?.ToInt64() ?? 0;
}
}
}
}

2
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)]

74
src/Avalonia.X11/X11Window.cs

@ -1460,5 +1460,79 @@ namespace Avalonia.X11
32, PropertyMode.Replace, new[] { atom }, 1);
}
/// <inheritdoc/>
public void GetWindowsZOrder(Span<Window> windows, Span<long> outputZOrder)
{
// a mapping of parent windows to their children, sorted by z-order (bottom to top)
var windowsChildren = new Dictionary<IntPtr, List<IntPtr>>();
var indexInWindowsSpan = new Dictionary<IntPtr, int>();
for (var i = 0; i < windows.Length; i++)
if (windows[i].PlatformImpl is { } platformImpl)
indexInWindowsSpan[platformImpl.Handle.Handle] = i;
foreach (var window in windows)
{
if (window.PlatformImpl is not X11Window x11Window)
continue;
var node = x11Window.Handle.Handle;
while (node != IntPtr.Zero)
{
if (windowsChildren.ContainsKey(node))
{
break;
}
if (XQueryTree(_x11.Display, node, out _, out var parent,
out var childrenPtr, out var childrenCount) == 0)
{
break;
}
if (childrenPtr != IntPtr.Zero)
{
var children = (IntPtr*)childrenPtr;
windowsChildren[node] = new List<IntPtr>(childrenCount);
for (var i = 0; i < childrenCount; i++)
{
windowsChildren[node].Add(children[i]);
}
XFree(childrenPtr);
}
node = parent;
}
}
var stack = new Stack<IntPtr>();
var zOrder = 0;
stack.Push(_x11.RootWindow);
while (stack.Count > 0)
{
var currentWindow = stack.Pop();
if (!windowsChildren.TryGetValue(currentWindow, out var children))
{
continue;
}
if (indexInWindowsSpan.TryGetValue(currentWindow, out var index))
{
outputZOrder[index] = zOrder;
}
zOrder++;
// 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 = children.Count - 1; i >= 0; i--)
{
stack.Push(children[i]);
}
}
}
}
}

16
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -16,12 +16,15 @@ namespace Avalonia.Headless
{
internal class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
{
private static int _nextGlobalZOrder = 1;
private readonly IKeyboardDevice _keyboard;
private readonly Stopwatch _st = Stopwatch.StartNew();
private readonly Pointer _mousePointer;
private WriteableBitmap? _lastRenderedFrame;
private readonly object _sync = new object();
private readonly PixelFormat _frameBufferFormat;
private int _zOrder;
public bool IsPopup { get; }
public HeadlessWindowImpl(bool isPopup, PixelFormat frameBufferFormat)
@ -80,7 +83,10 @@ namespace Avalonia.Headless
public void Show(bool activate, bool isDialog)
{
if (activate)
{
_zOrder = _nextGlobalZOrder++;
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
}
}
public void Hide()
@ -102,6 +108,7 @@ namespace Avalonia.Headless
public Action<PixelPoint>? PositionChanged { get; set; }
public void Activate()
{
_zOrder = _nextGlobalZOrder++;
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
}
@ -412,5 +419,14 @@ namespace Avalonia.Headless
{
}
public void GetWindowsZOrder(Span<Window> windows, Span<long> zOrder)
{
for (int i = 0; i < windows.Length; ++i)
{
if (windows[i].PlatformImpl is HeadlessWindowImpl headlessWindowImpl)
zOrder[i] = headlessWindowImpl._zOrder;
}
}
}
}

5
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1274,6 +1274,11 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SystemMetric smIndex);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool EnumChildWindows(IntPtr parentHwnd, EnumWindowsProc enumFunc, IntPtr lParam);
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW", ExactSpelling = true)]
public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);

34
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -184,6 +184,8 @@ namespace Avalonia.Win32
internal IInputRoot Owner
=> _owner ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called");
internal WindowImpl? ParentImpl => _parent;
public Action? Activated { get; set; }
public Func<WindowCloseReason, bool>? Closing { get; set; }
@ -1573,6 +1575,38 @@ namespace Avalonia.Win32
ExtendClientArea();
}
/// <inheritdoc/>
public void GetWindowsZOrder(Span<Window> windows, Span<long> zOrder)
{
var handlesToIndex = new Dictionary<IntPtr, int>(windows.Length);
var outputArray = new long[windows.Length];
for (int i = 0; i < windows.Length; i++)
{
if (windows[i].PlatformImpl is WindowImpl platformImpl)
handlesToIndex.Add(platformImpl.Handle.Handle, i);
}
long nextZOrder = 0;
bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam)
{
if (handlesToIndex.TryGetValue(hWnd, out var index))
{
// We negate the z-order so that the topmost window has the highest number.
outputArray[index] = -nextZOrder;
nextZOrder++;
}
return nextZOrder < outputArray.Length;
}
EnumChildWindows(IntPtr.Zero, EnumWindowsProc, IntPtr.Zero);
for (int i = 0; i < windows.Length; i++)
{
zOrder[i] = outputArray[i];
}
}
/// <inheritdoc/>
public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended;

Loading…
Cancel
Save