diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 38f5a7e8e1..b7efaf7869 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1225,6 +1225,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Window.SortWindowsByZOrder(Avalonia.Controls.Window[]) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Platform.IWindowImpl.GetWindowsZOrder(System.Span{Avalonia.Controls.Window},System.Span{System.Int64}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean) @@ -2125,6 +2137,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Window.SortWindowsByZOrder(Avalonia.Controls.Window[]) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Platform.IWindowImpl.GetWindowsZOrder(System.Span{Avalonia.Controls.Window},System.Span{System.Int64}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean) @@ -2377,6 +2401,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0006 + M:Avalonia.Platform.IWindowingPlatform.GetWindowsZOrder(System.ReadOnlySpan{Avalonia.Platform.IWindowImpl},System.Span{System.Int64}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0006 M:Avalonia.OpenGL.Surfaces.IGlPlatformSurfaceRenderTarget.BeginDraw(System.Nullable{Avalonia.PixelSize}) @@ -2575,6 +2605,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0006 + M:Avalonia.Platform.IWindowingPlatform.GetWindowsZOrder(System.ReadOnlySpan{Avalonia.Platform.IWindowImpl},System.Span{System.Int64}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0006 M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) diff --git a/src/Android/Avalonia.Android/Stubs.cs b/src/Android/Avalonia.Android/Stubs.cs index c79a23d8e7..0620919e4d 100644 --- a/src/Android/Avalonia.Android/Stubs.cs +++ b/src/Android/Avalonia.Android/Stubs.cs @@ -12,6 +12,8 @@ namespace Avalonia.Android public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException(); public ITrayIconImpl? CreateTrayIcon() => null; + + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) => throw new NotSupportedException(); } internal class PlatformIconLoaderStub : IPlatformIconLoader diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 50cb8c4f2d..a0740eb495 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -153,14 +153,5 @@ namespace Avalonia.Platform /// /// -1 for platform default, otherwise the height in DIPs. void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight); - - /// - /// 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. - /// - /// A span of windows to get their z-order - /// Span to be filled with associated window z-order - void GetWindowsZOrder(Span windows, Span zOrder); } } diff --git a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs index 09f490d50a..950af6ec96 100644 --- a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs +++ b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Metadata; namespace Avalonia.Platform @@ -12,5 +13,14 @@ namespace Avalonia.Platform IWindowImpl CreateEmbeddableWindow(); ITrayIconImpl? CreateTrayIcon(); + + /// + /// Fills a span with numbers that represent the relative order of the windows in the z-order. + /// The topmost window should have the highest number. + /// Both the and lists are expected to be the same length. + /// + /// A span of windows to get their z-order. + /// The span to be filled with the associated window z-order. + void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder); } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ee9671edda..65ac2897cc 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -903,24 +903,25 @@ namespace Avalonia.Controls /// /// Sorts the windows ascending by their Z order - the topmost window will be the last in the list. /// - /// - public static void SortWindowsByZOrder(Window[] windows) + /// The windows to sort. + public static void SortWindowsByZOrder(Span windows) { - if (windows.Length == 0) + if (windows.Length <= 1) return; - if (windows[0].PlatformImpl is not { } platformImpl) - throw new InvalidOperationException("Window.PlatformImpl is null"); - -#if NET5_0_OR_GREATER - Span 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 + var platform = AvaloniaLocator.Current.GetRequiredService(); + + var windowImpls = new IWindowImpl[windows.Length]; + for (var i = 0; i < windows.Length; ++i) + { + windowImpls[i] = windows[i].PlatformImpl ?? + throw new ArgumentException($"Invalid window at index {i}", nameof(windows)); + } + + const int stackAllocThreshold = 128; + var zOrder = windows.Length > stackAllocThreshold ? new long[windows.Length] : stackalloc long[windows.Length]; + platform.GetWindowsZOrder(windowImpls, zOrder); + zOrder.Sort(windows); } private void UpdateEnabled() diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 7733bcd4ff..8557ffcb30 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -170,7 +170,5 @@ namespace Avalonia.DesignerSupport.Remote public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) { } - - public void GetWindowsZOrder(Span windows, Span zOrder) => throw new NotSupportedException(); } } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index 301f8a7284..dcc24482c0 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -44,6 +44,9 @@ namespace Avalonia.DesignerSupport.Remote return s_lastWindow; } + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) + => zOrder.Clear(); + public static void Initialize(IAvaloniaRemoteTransportConnection transport) { s_transport = transport; diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index caa8bff00f..c50ea1e777 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -182,8 +182,6 @@ namespace Avalonia.DesignerSupport.Remote { } - public void GetWindowsZOrder(Span windows, Span zOrder) => throw new NotSupportedException(); - public IPopupPositioner? PopupPositioner { get; } public Action? GotInputWhenDisabled { get; set; } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 4ad0814283..915743e326 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using Avalonia.Compatibility; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -210,5 +211,13 @@ namespace Avalonia.Native { return new EmbeddableTopLevelImpl(_factory); } + + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) + { + for (var i = 0; i < windows.Length; i++) + { + zOrder[i] = (windows[i] as WindowImpl)?.ZOrder?.ToInt64() ?? 0; + } + } } } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 2f35231b4f..9d3752110c 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -271,13 +271,5 @@ namespace Avalonia.Native return base.TryGetFeature(featureType); } - - public void GetWindowsZOrder(Span windows, Span zOrder) - { - for (int i = 0; i < windows.Length; i++) - { - zOrder[i] = (windows[i].PlatformImpl as WindowImpl)?.ZOrder?.ToInt64() ?? 0; - } - } } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 6e129b6a65..62be01cbce 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -230,6 +230,83 @@ namespace Avalonia.X11 throw new InvalidOperationException($"{nameof(X11PlatformOptions)}.{nameof(X11PlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied."); } + + public void GetWindowsZOrder(ReadOnlySpan windows, Span outputZOrder) + { + // a mapping of parent windows to their children, sorted by z-order (bottom to top) + var windowsChildren = new Dictionary>(); + + var indexInWindowsSpan = new Dictionary(); + for (var i = 0; i < windows.Length; i++) + if (windows[i] is X11Window { Handle: { } handle }) + indexInWindowsSpan[handle.Handle] = i; + + foreach (var window in windows) + { + if (window is not X11Window x11Window) + continue; + + var node = x11Window.Handle.Handle; + while (node != IntPtr.Zero) + { + if (windowsChildren.ContainsKey(node)) + { + break; + } + + if (XQueryTree(Info.Display, node, out _, out var parent, + out var childrenPtr, out var childrenCount) == 0) + { + break; + } + + if (childrenPtr != IntPtr.Zero) + { + unsafe + { + var children = (IntPtr*)childrenPtr; + windowsChildren[node] = new List(childrenCount); + for (var i = 0; i < childrenCount; i++) + { + windowsChildren[node].Add(children[i]); + } + + XFree(childrenPtr); + } + } + + node = parent; + } + } + + var stack = new Stack(); + var zOrder = 0; + stack.Push(Info.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]); + } + } + } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index ab59882962..c931b1a6fb 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1557,80 +1557,6 @@ namespace Avalonia.X11 } - /// - public void GetWindowsZOrder(Span windows, Span outputZOrder) - { - // a mapping of parent windows to their children, sorted by z-order (bottom to top) - var windowsChildren = new Dictionary>(); - - var indexInWindowsSpan = new Dictionary(); - for (var i = 0; i < windows.Length; i++) - if (windows[i].PlatformImpl is { Handle: { } handle }) - indexInWindowsSpan[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(childrenCount); - for (var i = 0; i < childrenCount; i++) - { - windowsChildren[node].Add(children[i]); - } - XFree(childrenPtr); - } - - node = parent; - } - } - - var stack = new Stack(); - 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]); - } - } - } - public void TakeFocus() { // TODO: Not yet implemented: need to check if this is required on X11 or not. diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs index 0589b67c0d..33b9521ae2 100644 --- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -74,6 +74,9 @@ internal class BrowserWindowingPlatform : IWindowingPlatform return null; } + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) + => throw new NotSupportedException(); + public static KeyboardDevice Keyboard => s_keyboard ?? throw new InvalidOperationException("BrowserWindowingPlatform not registered."); diff --git a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index a423c9d866..667a8ba3ee 100644 --- a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -63,6 +63,14 @@ namespace Avalonia.Headless public IWindowImpl CreateEmbeddableWindow() => throw new PlatformNotSupportedException(); public ITrayIconImpl? CreateTrayIcon() => null; + + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) + { + for (var i = 0; i < windows.Length; ++i) + { + zOrder[i] = (windows[i] as HeadlessWindowImpl)?.ZOrder ?? 0; + } + } } internal static void Initialize(AvaloniaHeadlessPlatformOptions opts) diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index e3fc75eac4..8d88cebc13 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -25,7 +25,6 @@ namespace Avalonia.Headless 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) @@ -59,6 +58,8 @@ namespace Avalonia.Headless public Compositor Compositor => AvaloniaHeadlessPlatform.Compositor!; + public int ZOrder { get; private set; } + public void SetInputRoot(IInputRoot inputRoot) { InputRoot = inputRoot; @@ -82,7 +83,7 @@ namespace Avalonia.Headless { if (activate) { - _zOrder = _nextGlobalZOrder++; + ZOrder = _nextGlobalZOrder++; Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); } } @@ -96,7 +97,7 @@ namespace Avalonia.Headless public Action? PositionChanged { get; set; } public void Activate() { - _zOrder = _nextGlobalZOrder++; + ZOrder = _nextGlobalZOrder++; Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); } @@ -430,15 +431,6 @@ namespace Avalonia.Headless } - public void GetWindowsZOrder(Span windows, Span zOrder) - { - for (int i = 0; i < windows.Length; ++i) - { - if (windows[i].PlatformImpl is HeadlessWindowImpl headlessWindowImpl) - zOrder[i] = headlessWindowImpl._zOrder; - } - } - public void TakeFocus() { } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index b09872120f..41f273bc5e 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -323,5 +323,33 @@ namespace Avalonia.Win32 if (dpiAwareness != Win32DpiAwareness.Unaware) SetProcessDPIAware(); } + + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) + { + var handlesToIndex = new Dictionary(windows.Length); + var outputArray = new long[windows.Length]; + + for (int i = 0; i < windows.Length; i++) + { + if (windows[i] 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); + + outputArray.CopyTo(zOrder); + } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 63336f9d8f..081c488cd4 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1613,38 +1613,6 @@ namespace Avalonia.Win32 ExtendClientArea(); } - /// - public void GetWindowsZOrder(Span windows, Span zOrder) - { - var handlesToIndex = new Dictionary(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]; - } - } - /// public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; diff --git a/src/iOS/Avalonia.iOS/Stubs.cs b/src/iOS/Avalonia.iOS/Stubs.cs index 17202d4c9d..d0eb86a496 100644 --- a/src/iOS/Avalonia.iOS/Stubs.cs +++ b/src/iOS/Avalonia.iOS/Stubs.cs @@ -25,6 +25,9 @@ namespace Avalonia.iOS public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException(); public ITrayIconImpl? CreateTrayIcon() => null; + + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) + => throw new NotSupportedException(); } internal class PlatformIconLoaderStub : IPlatformIconLoader diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 9ddcb217ec..024d198967 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -162,5 +162,8 @@ namespace Avalonia.UnitTests { return null; } + + public void GetWindowsZOrder(ReadOnlySpan windows, Span zOrder) + => zOrder.Clear(); } }