From 75b0676d058de10a8bbb736f72378d3b50dac2bb Mon Sep 17 00:00:00 2001 From: shine <106159554+shine1258@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:58:02 +0800 Subject: [PATCH 01/86] Fix Placeholder not visible (#21024) * Add default PlaceholderForeground to AutoCompleteBox, CalendarDatePicker and NumericUpDown. * Add PlaceholderText template binding for simple theme ComboBox PART_EditableTextBox. --- src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml | 1 + src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml | 1 + src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml | 1 + src/Avalonia.Themes.Simple/Controls/ComboBox.xaml | 1 + src/Avalonia.Themes.Simple/Controls/NumericUpDown.xaml | 1 + 7 files changed, 7 insertions(+) diff --git a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml index 92c46ecd41..bd9c84af17 100644 --- a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml @@ -25,6 +25,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index 3a8db8ae36..395e2d514f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -63,6 +63,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml index b6c19484e9..bcd486534d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml @@ -52,6 +52,7 @@ + diff --git a/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml index ec0eccc8c4..7a1c98b598 100644 --- a/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml @@ -4,6 +4,7 @@ + diff --git a/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml index a38f6f912d..9eba0a413b 100644 --- a/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Simple/Controls/CalendarDatePicker.xaml @@ -12,6 +12,7 @@ + diff --git a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml index 5a6266af6e..be4725d08c 100644 --- a/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ComboBox.xaml @@ -50,6 +50,7 @@ Foreground="{TemplateBinding Foreground}" Background="Transparent" Text="{TemplateBinding Text, Mode=TwoWay}" + PlaceholderText="{TemplateBinding PlaceholderText}" BorderThickness="0" IsVisible="{TemplateBinding IsEditable}"> diff --git a/src/Avalonia.Themes.Simple/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Simple/Controls/NumericUpDown.xaml index 14cb0de6af..dbd01456bd 100644 --- a/src/Avalonia.Themes.Simple/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Simple/Controls/NumericUpDown.xaml @@ -4,6 +4,7 @@ + From 775d39d9aeb1bfb5434c5e726078c8bb4e08cf8c Mon Sep 17 00:00:00 2001 From: Dong Bin <14807942+rabbitism@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:02:55 +0800 Subject: [PATCH 02/86] Revise security policy and add vulnerability reporting (#21029) Updated the security policy to include vulnerability reporting guidelines and removed supported versions section. --- SECURITY.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..fcf55a12b9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in any of our SDKs, tools, services, or repositories, please help us keep the community safe by reporting it responsibly. + +You can report security vulnerabilities by emailing **[security@avaloniaui.net](mailto:security@avaloniaui.net)**. + +Please avoid disclosing the issue publicly until we have had a reasonable amount of time to investigate and release a patch or mitigation. We review all legitimate reports and will work with you to quickly resolve the issue. + +Please note that Avalonia does not operate a bug bounty programme. From 23baa0a0433ccda53b59d39fa0e3ab4848a85d91 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 30 Mar 2026 13:54:32 +0500 Subject: [PATCH 03/86] Added explicit background processing support to managed dispatcher impl (#21025) --- .../Platform/ManagedDispatcherImpl.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Platform/ManagedDispatcherImpl.cs b/src/Avalonia.Base/Platform/ManagedDispatcherImpl.cs index c8225f775b..fc1cfbf5b1 100644 --- a/src/Avalonia.Base/Platform/ManagedDispatcherImpl.cs +++ b/src/Avalonia.Base/Platform/ManagedDispatcherImpl.cs @@ -7,7 +7,7 @@ using Avalonia.Threading; namespace Avalonia.Controls.Platform; [Unstable] -public class ManagedDispatcherImpl : IControlledDispatcherImpl +public class ManagedDispatcherImpl : IControlledDispatcherImpl, IDispatcherImplWithExplicitBackgroundProcessing { private readonly IManagedDispatcherInputProvider? _inputProvider; private readonly AutoResetEvent _wakeup = new(false); @@ -16,6 +16,7 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl private readonly Stopwatch _clock = Stopwatch.StartNew(); private TimeSpan? _nextTimer; private readonly Thread _loopThread = Thread.CurrentThread; + private bool _backgroundProcessingRequested; public interface IManagedDispatcherInputProvider { @@ -56,6 +57,22 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl public bool CanQueryPendingInput => _inputProvider != null; public bool HasPendingInput => _inputProvider?.HasInput ?? false; + private Action? ReadyForBackgroundProcessing { get; set; } + event Action? IDispatcherImplWithExplicitBackgroundProcessing.ReadyForBackgroundProcessing + { + add => ReadyForBackgroundProcessing += value; + remove => ReadyForBackgroundProcessing -= value; + } + + void IDispatcherImplWithExplicitBackgroundProcessing.RequestBackgroundProcessing() + { + lock (_lock) + { + _backgroundProcessingRequested = true; + _wakeup.Set(); + } + } + public void RunLoop(CancellationToken token) { CancellationTokenRegistration registration = default; @@ -98,6 +115,19 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl _inputProvider.DispatchNextInputEvent(); continue; } + + bool triggerBackgroundProcessing; + lock (_lock) + { + triggerBackgroundProcessing = _backgroundProcessingRequested; + _backgroundProcessingRequested = false; + } + + if (triggerBackgroundProcessing) + { + ReadyForBackgroundProcessing?.Invoke(); + continue; + } TimeSpan? nextTimer; lock (_lock) From ebbf9c00fc3a1af1c957be0f52e8f1376dccced2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 30 Mar 2026 14:13:04 +0500 Subject: [PATCH 04/86] Combine Ready/Corrupted into a single passthrough struct (#21028) * Combine Ready/Corrupted into a single passthrough struct * api diff --- api/Avalonia.Skia.nupkg.xml | 28 ++++++++++- api/Avalonia.Win32.nupkg.xml | 16 +++++++ api/Avalonia.nupkg.xml | 48 +++++++++++++++++++ src/Avalonia.Base/Platform/IRenderTarget.cs | 9 +--- .../Platform/RenderTargetProperties.cs | 30 ++++++++++++ .../Surfaces/IPlatformRenderSurface.cs | 2 +- .../Server/ServerCompositionTarget.cs | 4 +- .../AvaloniaNativeGlPlatformGraphics.cs | 2 +- .../Egl/EglGlPlatformSurfaceBase.cs | 3 ++ .../IGlPlatformSurfaceRenderTarget.cs | 1 - .../VulkanKhrSurfaceRenderTarget.cs | 3 ++ src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs | 2 +- .../HeadlessPlatformRenderInterface.cs | 2 - .../Output/DrmOutput.cs | 2 +- .../Avalonia.Skia/FramebufferRenderTarget.cs | 9 ++-- .../Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs | 4 +- .../Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs | 4 +- .../Gpu/OpenGl/GlRenderTarget.cs | 4 +- .../Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs | 5 +- .../Gpu/Vulkan/VulkanSkiaRenderTarget.cs | 4 +- .../DirectCompositedWindowSurface.cs | 6 +-- .../IDirect3D11TexturePlatformSurface.cs | 4 +- .../OpenGl/Angle/AngleD3DTextureFeature.cs | 3 +- .../OpenGl/WglGlPlatformSurface.cs | 2 +- .../WinUiCompositedWindowSurface.cs | 5 +- 25 files changed, 154 insertions(+), 48 deletions(-) create mode 100644 api/Avalonia.Win32.nupkg.xml diff --git a/api/Avalonia.Skia.nupkg.xml b/api/Avalonia.Skia.nupkg.xml index b73745af8e..7a83006bf2 100644 --- a/api/Avalonia.Skia.nupkg.xml +++ b/api/Avalonia.Skia.nupkg.xml @@ -1,4 +1,4 @@ - + @@ -61,6 +61,18 @@ baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + + CP0002 + M:Avalonia.Skia.ISkiaGpuRenderTarget.get_IsCorrupted + baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + + + CP0002 + M:Avalonia.Skia.ISkiaGpuRenderTarget.get_IsReady + baseline/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net10.0/Avalonia.Skia.dll + CP0002 M:Avalonia.Skia.Helpers.DrawingContextHelper.WrapSkiaCanvas(SkiaSharp.SKCanvas,Avalonia.Vector) @@ -85,6 +97,18 @@ baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + + CP0002 + M:Avalonia.Skia.ISkiaGpuRenderTarget.get_IsCorrupted + baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + + + CP0002 + M:Avalonia.Skia.ISkiaGpuRenderTarget.get_IsReady + baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll + CP0006 M:Avalonia.Skia.ISkiaGpu.TryCreateRenderTarget(System.Collections.Generic.IEnumerable{Avalonia.Platform.Surfaces.IPlatformRenderSurface}) @@ -169,4 +193,4 @@ baseline/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll current/Avalonia.Skia/lib/net8.0/Avalonia.Skia.dll - + \ No newline at end of file diff --git a/api/Avalonia.Win32.nupkg.xml b/api/Avalonia.Win32.nupkg.xml new file mode 100644 index 0000000000..903595add8 --- /dev/null +++ b/api/Avalonia.Win32.nupkg.xml @@ -0,0 +1,16 @@ + + + + + CP0002 + M:Avalonia.Win32.DirectX.IDirect3D11TextureRenderTarget.get_IsCorrupted + baseline/Avalonia.Win32/lib/net10.0/Avalonia.Win32.dll + current/Avalonia.Win32/lib/net10.0/Avalonia.Win32.dll + + + CP0002 + M:Avalonia.Win32.DirectX.IDirect3D11TextureRenderTarget.get_IsCorrupted + baseline/Avalonia.Win32/lib/net8.0/Avalonia.Win32.dll + current/Avalonia.Win32/lib/net8.0/Avalonia.Win32.dll + + \ No newline at end of file diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 71bdf3714d..f3a9929ca1 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -1693,12 +1693,30 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.IRenderTarget.get_IsCorrupted + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Platform.IRenderTarget.get_IsReady + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action) baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.Surfaces.IPlatformRenderSurfaceRenderTarget.get_IsReady + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.get_ImportCompeted @@ -2587,6 +2605,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.OpenGL.dll current/Avalonia/lib/net10.0/Avalonia.OpenGL.dll + + CP0002 + M:Avalonia.OpenGL.Surfaces.IGlPlatformSurfaceRenderTarget.get_IsCorrupted + baseline/Avalonia/lib/net10.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net10.0/Avalonia.OpenGL.dll + CP0002 M:Avalonia.Vulkan.IVulkanKhrSurfacePlatformSurfaceFactory.CanRenderToSurface(Avalonia.Vulkan.IVulkanPlatformGraphicsContext,System.Object) @@ -3367,12 +3391,30 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.IRenderTarget.get_IsCorrupted + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Platform.IRenderTarget.get_IsReady + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action) baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.Surfaces.IPlatformRenderSurfaceRenderTarget.get_IsReady + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.get_ImportCompeted @@ -4267,6 +4309,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + + CP0002 + M:Avalonia.OpenGL.Surfaces.IGlPlatformSurfaceRenderTarget.get_IsCorrupted + baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll + CP0002 M:Avalonia.Vulkan.IVulkanKhrSurfacePlatformSurfaceFactory.CanRenderToSurface(Avalonia.Vulkan.IVulkanPlatformGraphicsContext,System.Object) diff --git a/src/Avalonia.Base/Platform/IRenderTarget.cs b/src/Avalonia.Base/Platform/IRenderTarget.cs index e66d14995e..2e7d56405a 100644 --- a/src/Avalonia.Base/Platform/IRenderTarget.cs +++ b/src/Avalonia.Base/Platform/IRenderTarget.cs @@ -12,11 +12,6 @@ namespace Avalonia.Platform [PrivateApi] public interface IRenderTarget : IDisposable { - /// - /// Indicates if the render target is no longer usable and needs to be recreated - /// - bool IsCorrupted { get; } - /// /// Gets the properties of the render target. /// @@ -33,9 +28,9 @@ namespace Avalonia.Platform IDrawingContextImpl CreateDrawingContext(RenderTargetSceneInfo sceneInfo, out RenderTargetDrawingContextProperties properties); /// - /// Indicates if the render target is currently ready to be rendered to + /// Gets the current readiness state of the render target. /// - bool IsReady => true; + PlatformRenderTargetState PlatformRenderTargetState => PlatformRenderTargetState.Ready; public record struct RenderTargetSceneInfo(PixelSize Size, double Scaling); } diff --git a/src/Avalonia.Base/Platform/RenderTargetProperties.cs b/src/Avalonia.Base/Platform/RenderTargetProperties.cs index c4a0948180..de4f3f4345 100644 --- a/src/Avalonia.Base/Platform/RenderTargetProperties.cs +++ b/src/Avalonia.Base/Platform/RenderTargetProperties.cs @@ -3,6 +3,36 @@ using Avalonia.Metadata; namespace Avalonia.Platform; +/// +/// Describes the current readiness state of a platform render target. +/// Flows through the entire rendering pipeline from platform to compositor. +/// +[PrivateApi] +[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Private API, not meant to be compared")] +public readonly struct PlatformRenderTargetState +{ + /// + /// Indicates if the render target is currently ready to be rendered to. + /// + public bool IsReady { get; init; } + + /// + /// Indicates if the render target is no longer usable and needs to be recreated + /// + public bool IsCorrupted { get; init; } + + /// + /// A readiness state indicating the target is ready to render. + /// + public static PlatformRenderTargetState Ready => new() { IsReady = true }; + + public static PlatformRenderTargetState NotReadyTryLater => default; + + public static PlatformRenderTargetState Corrupted => new() { IsCorrupted = true, IsReady = true}; + + public static PlatformRenderTargetState Disposed => new() { IsCorrupted = true}; +} + [PrivateApi] [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Private API, not meant to be compared")] public struct RenderTargetProperties diff --git a/src/Avalonia.Base/Platform/Surfaces/IPlatformRenderSurface.cs b/src/Avalonia.Base/Platform/Surfaces/IPlatformRenderSurface.cs index ff71a700c4..0c085c20b8 100644 --- a/src/Avalonia.Base/Platform/Surfaces/IPlatformRenderSurface.cs +++ b/src/Avalonia.Base/Platform/Surfaces/IPlatformRenderSurface.cs @@ -11,5 +11,5 @@ public interface IPlatformRenderSurface [PrivateApi] public interface IPlatformRenderSurfaceRenderTarget { - bool IsReady => true; + PlatformRenderTargetState State => PlatformRenderTargetState.Ready; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index e8ae84eb03..fe857ac466 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -138,7 +138,7 @@ namespace Avalonia.Rendering.Composition.Server if (Root == null) return; - if (_renderTarget?.IsCorrupted == true) + if (_renderTarget?.PlatformRenderTargetState.IsCorrupted == true) { _layer?.Dispose(); _layer = null; @@ -178,7 +178,7 @@ namespace Avalonia.Rendering.Composition.Server if (!_redrawRequested) return; - if (!_renderTarget.IsReady) + if (!_renderTarget.PlatformRenderTargetState.IsReady) { IsWaitingForReadyRenderTarget = IsEnabled; return; diff --git a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs index 91e4dd2680..9efe33689f 100644 --- a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs +++ b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs @@ -173,7 +173,7 @@ namespace Avalonia.Native _context = context; } - public bool IsCorrupted => false; + public PlatformRenderTargetState State => PlatformRenderTargetState.Ready; public IGlPlatformSurfaceRenderingSession BeginDraw(IRenderTarget.RenderTargetSceneInfo sceneInfo) { diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs index ef4ae257bd..8ee06266d8 100644 --- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs @@ -110,6 +110,9 @@ namespace Avalonia.OpenGL.Egl public bool IsYFlipped { get; } } + public virtual PlatformRenderTargetState State => + IsCorrupted ? PlatformRenderTargetState.Corrupted : PlatformRenderTargetState.Ready; + public virtual bool IsCorrupted => Context.IsLost; } } diff --git a/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs index da35de6649..86c733c5fa 100644 --- a/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs @@ -8,7 +8,6 @@ namespace Avalonia.OpenGL.Surfaces [PrivateApi] public interface IGlPlatformSurfaceRenderTarget : IDisposable, IPlatformRenderSurfaceRenderTarget { - bool IsCorrupted { get; } IGlPlatformSurfaceRenderingSession BeginDraw(IRenderTarget.RenderTargetSceneInfo sceneInfo); } } diff --git a/src/Avalonia.Vulkan/VulkanKhrSurfaceRenderTarget.cs b/src/Avalonia.Vulkan/VulkanKhrSurfaceRenderTarget.cs index a24b9eea7a..64aaa5ad30 100644 --- a/src/Avalonia.Vulkan/VulkanKhrSurfaceRenderTarget.cs +++ b/src/Avalonia.Vulkan/VulkanKhrSurfaceRenderTarget.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Platform; using Avalonia.Vulkan.Interop; using Avalonia.Vulkan.UnmanagedInterop; @@ -26,6 +27,8 @@ internal class VulkanKhrRenderTarget : IVulkanRenderTarget Format = IsRgba ? VkFormat.VK_FORMAT_R8G8B8A8_UNORM : VkFormat.VK_FORMAT_B8G8R8A8_UNORM; } + public PlatformRenderTargetState State => PlatformRenderTargetState.Ready; + private void CreateImage() { _image = new VulkanImage(_context, _display.CommandBufferPool, Format, _display.Size); diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs index 81d7a4e9bc..b91c7ac6c9 100644 --- a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs +++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs @@ -39,7 +39,7 @@ namespace Avalonia.X11.Glx // No-op } - public bool IsCorrupted => false; + public PlatformRenderTargetState State => PlatformRenderTargetState.Ready; public IGlPlatformSurfaceRenderingSession BeginDraw(IRenderTarget.RenderTargetSceneInfo sceneInfo) { var size = sceneInfo.Size; diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index b83cf8b526..1c152d0fe4 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -608,8 +608,6 @@ namespace Avalonia.Headless properties = default; return new HeadlessDrawingContextStub(); } - - public bool IsCorrupted => false; } public void Dispose() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index cf0bf1e49a..dabf1b6bfd 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -407,7 +407,7 @@ namespace Avalonia.LinuxFramebuffer.Output _parent = parent; } - public bool IsCorrupted => false; + public PlatformRenderTargetState State => PlatformRenderTargetState.Ready; public void Dispose() { diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 5491df93b8..828de2843f 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -47,8 +47,8 @@ namespace Avalonia.Skia IsSuitableForDirectRendering = true }; - - + public PlatformRenderTargetState PlatformRenderTargetState => + _renderTarget?.State ?? PlatformRenderTargetState.Disposed; /// public IDrawingContextImpl CreateDrawingContext(IRenderTarget.RenderTargetSceneInfo sceneInfo, @@ -86,10 +86,7 @@ namespace Avalonia.Skia return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer); } - - public bool IsCorrupted => false; - - public bool IsReady => _renderTarget is not IPlatformRenderSurfaceRenderTarget prs || prs.IsReady; + /// /// Check if two images info are compatible. diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs index 65b6b5a8df..bd8061cf98 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs @@ -15,8 +15,6 @@ namespace Avalonia.Skia /// A render session instance. ISkiaGpuRenderSession BeginRenderingSession(IRenderTarget.RenderTargetSceneInfo sceneInfo); - bool IsCorrupted { get; } - - bool IsReady => true; + PlatformRenderTargetState State => PlatformRenderTargetState.Ready; } } diff --git a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs index 640b415d6f..402d83fd10 100644 --- a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs @@ -108,9 +108,7 @@ internal class SkiaMetalGpu : ISkiaGpu return new SkiaMetalRenderSession(_gpu, surface, session, backendTarget); } - public bool IsCorrupted => false; - - public bool IsReady => _target?.IsReady ?? false; + public PlatformRenderTargetState State => _target?.State ?? PlatformRenderTargetState.Disposed; } internal class SkiaMetalRenderSession : ISkiaGpuRenderSession diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index f5c164be45..69e2f0563e 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -21,9 +21,7 @@ namespace Avalonia.Skia public void Dispose() => _surface.Dispose(); - public bool IsCorrupted => _surface.IsCorrupted; - - public bool IsReady => _surface.IsReady; + public PlatformRenderTargetState State => _surface.State; class GlGpuSession : ISkiaGpuRenderSession { diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index 13bd946b74..d748018824 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -43,9 +43,8 @@ namespace Avalonia.Skia return new DrawingContextImpl(nfo, session); } - - public bool IsCorrupted => _renderTarget.IsCorrupted; - public bool IsReady => _renderTarget.IsReady; + + public PlatformRenderTargetState PlatformRenderTargetState => _renderTarget.State; public RenderTargetProperties Properties { get; } diff --git a/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaRenderTarget.cs index 6b04fcd8c0..dc229e16e8 100644 --- a/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaRenderTarget.cs @@ -74,9 +74,7 @@ class VulkanSkiaRenderTarget : ISkiaGpuRenderTarget } } - public bool IsCorrupted => false; - - public bool IsReady => _target.IsReady; + public PlatformRenderTargetState State => _target.State; internal class VulkanSkiaRenderSession : ISkiaGpuRenderSession diff --git a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs index d0651e08ee..9c6f5707bf 100644 --- a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs @@ -78,11 +78,11 @@ internal class DirectCompositedWindowRenderTarget : IDirect3D11TextureRenderTarg _d3dDevice.Dispose(); } - public bool IsCorrupted => _context.IsLost || _lost; - + public PlatformRenderTargetState State => _context.IsLost || _lost ? PlatformRenderTargetState.Corrupted : PlatformRenderTargetState.Ready; + public unsafe IDirect3D11TextureRenderTargetRenderSession BeginDraw() { - if (IsCorrupted) + if (State.IsCorrupted) throw new RenderTargetCorruptedException(); var transaction = _window.BeginTransaction(); bool needsEndDraw = false; diff --git a/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs b/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs index fb1a6a27e7..ce077d6ce0 100644 --- a/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs @@ -1,5 +1,6 @@ using System; using Avalonia.OpenGL; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Platform.Surfaces; @@ -12,9 +13,8 @@ public interface IDirect3D11TexturePlatformSurface : IPlatformRenderSurface -public interface IDirect3D11TextureRenderTarget : IDisposable +public interface IDirect3D11TextureRenderTarget : IPlatformRenderSurfaceRenderTarget, IDisposable { - bool IsCorrupted { get; } IDirect3D11TextureRenderTargetRenderSession BeginDraw(); } diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs index 0dc8ec453e..60882fb62e 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs @@ -83,7 +83,8 @@ internal class AngleD3DTextureFeature : IGlPlatformSurfaceRenderTargetFactory base.Dispose(); } - public override bool IsCorrupted => _target.IsCorrupted || base.IsCorrupted; + public override PlatformRenderTargetState State => + base.IsCorrupted ? PlatformRenderTargetState.Corrupted : _target.State; } public IGlPlatformSurfaceRenderTarget CreateRenderTarget(IGlContext context, IPlatformRenderSurface surface) diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs index f54ca9b62c..39a7da74ca 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs @@ -37,7 +37,7 @@ namespace Avalonia.Win32.OpenGl _hdc = context.CreateConfiguredDeviceContext(info.Handle); } - public bool IsCorrupted => false; + public PlatformRenderTargetState State => PlatformRenderTargetState.Ready; public void Dispose() { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 2addbc6524..96e5fae00b 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -114,11 +114,12 @@ namespace Avalonia.Win32.WinRT.Composition _d3dDevice.Dispose(); } - public bool IsCorrupted => _context.IsLost || _lost; + public PlatformRenderTargetState State => + _context.IsLost || _lost ? PlatformRenderTargetState.Corrupted : PlatformRenderTargetState.Ready; public unsafe IDirect3D11TextureRenderTargetRenderSession BeginDraw() { - if (IsCorrupted) + if (State.IsCorrupted) throw new RenderTargetCorruptedException(); var transaction = _window.BeginTransaction(); bool needsEndDraw = false; From b2ee6119fde635c2ca9cf179162391e9afebb4af Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 30 Mar 2026 14:26:35 +0500 Subject: [PATCH 05/86] Change XInternAtoms call force-create atoms (#21034) --- src/tools/DevGenerators/X11AtomsGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/DevGenerators/X11AtomsGenerator.cs b/src/tools/DevGenerators/X11AtomsGenerator.cs index 920b3477dc..e91016af3e 100644 --- a/src/tools/DevGenerators/X11AtomsGenerator.cs +++ b/src/tools/DevGenerators/X11AtomsGenerator.cs @@ -72,7 +72,7 @@ public class X11AtomsGenerator : IIncrementalGenerator classBuilder.Pad(3).Append("\"").Append(writeableFields[c].Name).AppendLine("\","); classBuilder.Pad(2).AppendLine("};"); - classBuilder.Pad(2).AppendLine("XInternAtoms(display, atomNames, atomNames.Length, true, atoms);"); + classBuilder.Pad(2).AppendLine("XInternAtoms(display, atomNames, atomNames.Length, false, atoms);"); for (int c = 0; c < writeableFields.Count; c++) classBuilder.Pad(2).Append("InitAtom(ref ").Append(writeableFields[c].Name).Append(", \"") From 94c809f84eef0dbb08f84ce11ddbee3cffdd93dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Mon, 30 Mar 2026 16:18:25 +0200 Subject: [PATCH 06/86] Rename AppBar controls (#21041) * Renamed AppBar buttons and separator * CommandBarSeparator inherit from Separator * Added suppressions --- api/Avalonia.nupkg.xml | 36 ++++ .../CommandBarCustomizationPage.xaml | 24 +-- .../CommandBarDynamicOverflowPage.xaml | 12 +- .../CommandBar/CommandBarEventsPage.xaml | 10 +- .../CommandBar/CommandBarEventsPage.xaml.cs | 18 +- .../CommandBar/CommandBarFirstLookPage.xaml | 56 +++--- .../CommandBarFirstLookPage.xaml.cs | 4 +- .../CommandBarLabelPositionPage.xaml | 24 +-- .../CommandBar/CommandBarOverflowPage.xaml | 10 +- .../CommandBar/CommandBarOverflowPage.xaml.cs | 4 +- .../CommandBar/CommandBarTogglePage.xaml | 28 +-- .../Pages/CommandBarPage.xaml.cs | 2 +- .../ContentPageCommandBarPage.xaml.cs | 20 +- .../NavigationPageToolbarPage.xaml.cs | 16 +- .../CommandBar/CommandBar.cs | 12 +- .../{AppBarButton.cs => CommandBarButton.cs} | 20 +- ...BarSeparator.cs => CommandBarSeparator.cs} | 8 +- ...gleButton.cs => CommandBarToggleButton.cs} | 20 +- .../Controls/CommandBar.xaml | 38 ++-- .../Controls/NavigationPage.xaml | 4 +- .../Controls/CommandBar.xaml | 28 +-- .../CommandBarTests.cs | 182 +++++++++--------- .../Controls/CommandBarTests.cs | 16 +- .../Controls/ContentPageTests.cs | 8 +- 24 files changed, 319 insertions(+), 281 deletions(-) rename src/Avalonia.Controls/CommandBar/{AppBarButton.cs => CommandBarButton.cs} (80%) rename src/Avalonia.Controls/CommandBar/{AppBarSeparator.cs => CommandBarSeparator.cs} (81%) rename src/Avalonia.Controls/CommandBar/{AppBarToggleButton.cs => CommandBarToggleButton.cs} (79%) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index f3a9929ca1..a63ac3baae 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -5821,4 +5821,40 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0001 + T:Avalonia.Controls.AppBarButton + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarSeparator + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarToggleButton + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarButton + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarSeparator + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarToggleButton + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml index 1ea3349129..3ce911d9f8 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarCustomizationPage.xaml @@ -15,9 +15,9 @@ - - - + + + @@ -74,7 +74,7 @@ - @@ -89,20 +89,20 @@ - - + + - - + + - - + + @@ -111,8 +111,8 @@ BorderBrush="#CCCCCC" BorderThickness="1" OverflowButtonVisibility="Collapsed"> - - + + diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml index 2f771ab42e..6d66c19c96 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml @@ -61,12 +61,12 @@ ClipToBounds="True"> - - - - - - + + + + + + diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml index 8dbc44e19b..b4728aa918 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml @@ -64,13 +64,13 @@ - - - + + + - - + + diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml.cs index c2f0b439f0..f82fcbddb9 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarEventsPage.xaml.cs @@ -141,13 +141,13 @@ namespace ControlCatalog.Pages private void OnCommandItemClick(object? sender, RoutedEventArgs e) { - if (sender is AppBarButton button) + if (sender is CommandBarButton button) AppendLog($"Click, {button.Label}, {DescribePlacement(button)}"); } - private AppBarButton CreateButton(string label) + private CommandBarButton CreateButton(string label) { - var button = new AppBarButton + var button = new CommandBarButton { Label = label, Icon = new PathIcon @@ -170,14 +170,14 @@ namespace ControlCatalog.Pages { foreach (var item in items) { - if (item is AppBarButton button) + if (item is CommandBarButton button) button.Click -= OnCommandItemClick; } } private void AttachItemHandler(ICommandBarElement item) { - if (item is not AppBarButton button) + if (item is not CommandBarButton button) return; button.Click -= OnCommandItemClick; @@ -191,10 +191,10 @@ namespace ControlCatalog.Pages return; var item = items[^1]; - var label = item is AppBarButton button ? button.Label ?? "(unnamed)" : item.GetType().Name; + var label = item is CommandBarButton button ? button.Label ?? "(unnamed)" : item.GetType().Name; - if (item is AppBarButton appBarButton) - appBarButton.Click -= OnCommandItemClick; + if (item is CommandBarButton commandBarButton) + commandBarButton.Click -= OnCommandItemClick; items.RemoveAt(items.Count - 1); @@ -202,7 +202,7 @@ namespace ControlCatalog.Pages RefreshState(); } - private static string DescribePlacement(AppBarButton button) + private static string DescribePlacement(CommandBarButton button) { return button.IsInOverflow ? "overflow" : "primary"; } diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml index b83d1c3e57..60c23d9c64 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml @@ -36,7 +36,7 @@ - @@ -52,35 +52,35 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml.cs index c624ffcad6..9e15c0fb6c 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarFirstLookPage.xaml.cs @@ -12,13 +12,13 @@ namespace ControlCatalog.Pages private void OnButtonClick(object? sender, RoutedEventArgs e) { - if (sender is AppBarButton btn) + if (sender is CommandBarButton btn) StatusText.Text = $"{btn.Label} clicked"; } private void OnToggleChanged(object? sender, RoutedEventArgs e) { - if (sender is AppBarToggleButton btn) + if (sender is CommandBarToggleButton btn) StatusText.Text = btn.IsChecked == true ? $"{btn.Label} enabled" : $"{btn.Label} disabled"; diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml index a1dbea2db1..a4b23542d7 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarLabelPositionPage.xaml @@ -15,10 +15,10 @@ - - - - + + + + @@ -26,10 +26,10 @@ - - - - + + + + @@ -37,10 +37,10 @@ - - - - + + + + diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml index be3371a8e8..46bcac3fac 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml @@ -59,13 +59,13 @@ - - - + + + - - + + diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml.cs index a0d5f24b7f..ff5ee07f2a 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarOverflowPage.xaml.cs @@ -42,13 +42,13 @@ namespace ControlCatalog.Pages private void OnAddPrimary(object? sender, RoutedEventArgs e) { _primaryCount++; - DemoBar.PrimaryCommands.Add(new AppBarButton { Label = $"Cmd {_primaryCount}" }); + DemoBar.PrimaryCommands.Add(new CommandBarButton { Label = $"Cmd {_primaryCount}" }); } private void OnAddSecondary(object? sender, RoutedEventArgs e) { _secondaryCount++; - DemoBar.SecondaryCommands.Add(new AppBarButton { Label = $"Sec {_secondaryCount}" }); + DemoBar.SecondaryCommands.Add(new CommandBarButton { Label = $"Sec {_secondaryCount}" }); } } } diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml index d2513038c0..794bd3fcd4 100644 --- a/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarTogglePage.xaml @@ -28,7 +28,7 @@ - @@ -37,25 +37,25 @@ - AppBarToggleButton supports on/off states for formatting and feature toggles. + CommandBarToggleButton supports on/off states for formatting and feature toggles. - - - - + + - - - + + - - + + - - - + + + diff --git a/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs index 7aaf6d22be..e76d605645 100644 --- a/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs @@ -13,7 +13,7 @@ namespace ControlCatalog.Pages { // Overview ("Overview", "First Look", "A CommandBar with primary commands, secondary overflow menu, and custom content area.", () => new CommandBarFirstLookPage()), - ("Overview", "Toggle Buttons", "AppBarToggleButton for stateful actions like Bold, Italic, and Favorite.", () => new CommandBarTogglePage()), + ("Overview", "Toggle Buttons", "CommandBarToggleButton for stateful actions like Bold, Italic, and Favorite.", () => new CommandBarTogglePage()), // Appearance ("Appearance", "Label Positions", "Configure label position: Bottom (default), Right, or Collapsed (icon only).", () => new CommandBarLabelPositionPage()), diff --git a/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml.cs b/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml.cs index 07bdbb52a2..3bb6b30082 100644 --- a/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContentPage/ContentPageCommandBarPage.xaml.cs @@ -78,7 +78,7 @@ namespace ControlCatalog.Pages private void OnAddPrimary(object? sender, RoutedEventArgs e) { _itemCounter++; - var btn = new AppBarButton { Label = $"Action {_itemCounter}" }; + var btn = new CommandBarButton { Label = $"Action {_itemCounter}" }; if (UseIconCheck.IsChecked == true) { var preset = IconPresets[(_itemCounter - 1) % IconPresets.Length]; @@ -93,13 +93,13 @@ namespace ControlCatalog.Pages private void OnAddSecondary(object? sender, RoutedEventArgs e) { _itemCounter++; - _secondaryItems.Add(new AppBarButton { Label = $"Item {_itemCounter}" }); + _secondaryItems.Add(new CommandBarButton { Label = $"Item {_itemCounter}" }); RebuildCommandBar(); } private void OnAddSeparator(object? sender, RoutedEventArgs e) { - _primaryItems.Add(new AppBarSeparator()); + _primaryItems.Add(new CommandBarSeparator()); RebuildCommandBar(); } @@ -177,26 +177,26 @@ namespace ControlCatalog.Pages foreach (var item in _primaryItems) { - if (item is AppBarButton btn) + if (item is CommandBarButton btn) { PathIcon? icon = null; if (btn.Icon is PathIcon src) icon = new PathIcon { Data = src.Data }; - commandBar.PrimaryCommands.Add(new AppBarButton + commandBar.PrimaryCommands.Add(new CommandBarButton { Label = btn.Label, Icon = icon, IsCompact = btn.IsCompact, }); } - else if (item is AppBarSeparator) - commandBar.PrimaryCommands.Add(new AppBarSeparator()); + else if (item is CommandBarSeparator) + commandBar.PrimaryCommands.Add(new CommandBarSeparator()); } foreach (var item in _secondaryItems) { - if (item is AppBarButton btn) - commandBar.SecondaryCommands.Add(new AppBarButton { Label = btn.Label }); + if (item is CommandBarButton btn) + commandBar.SecondaryCommands.Add(new CommandBarButton { Label = btn.Label }); } if (_position == "Top") @@ -204,7 +204,7 @@ namespace ControlCatalog.Pages else NavigationPage.SetBottomCommandBar(activePage, commandBar); - var primaryCount = _primaryItems.Count(i => i is AppBarButton); + var primaryCount = _primaryItems.Count(i => i is CommandBarButton); var secondaryCount = _secondaryItems.Count; StatusText.Text = $"{primaryCount} primary, {secondaryCount} secondary ({_position})"; } diff --git a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageToolbarPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageToolbarPage.xaml.cs index c141f47781..724f8de84a 100644 --- a/samples/ControlCatalog/Pages/NavigationPage/NavigationPageToolbarPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NavigationPage/NavigationPageToolbarPage.xaml.cs @@ -49,7 +49,7 @@ namespace ControlCatalog.Pages private void OnAddPrimary(object? sender, RoutedEventArgs e) { _itemCount++; - _rootCommandBar.PrimaryCommands.Add(new AppBarButton + _rootCommandBar.PrimaryCommands.Add(new CommandBarButton { Label = $"Item {_itemCount}", Icon = new PathIcon { Data = (Geometry)this.FindResource("AddIcon")! } @@ -60,7 +60,7 @@ namespace ControlCatalog.Pages private void OnAddSecondary(object? sender, RoutedEventArgs e) { _itemCount++; - _rootCommandBar.SecondaryCommands.Add(new AppBarButton + _rootCommandBar.SecondaryCommands.Add(new CommandBarButton { Label = $"Secondary {_itemCount}" }); @@ -69,13 +69,13 @@ namespace ControlCatalog.Pages private void OnAddPrimarySeparator(object? sender, RoutedEventArgs e) { - _rootCommandBar.PrimaryCommands.Add(new AppBarSeparator()); + _rootCommandBar.PrimaryCommands.Add(new CommandBarSeparator()); UpdateStatus(); } private void OnAddSecondarySeparator(object? sender, RoutedEventArgs e) { - _rootCommandBar.SecondaryCommands.Add(new AppBarSeparator()); + _rootCommandBar.SecondaryCommands.Add(new CommandBarSeparator()); UpdateStatus(); } @@ -125,13 +125,13 @@ namespace ControlCatalog.Pages { PrimaryCommands = { - new AppBarButton { Label = "Search", Icon = new PathIcon { Data = (Geometry)this.FindResource("SearchIcon")! } }, - new AppBarButton { Label = "Share", Icon = new PathIcon { Data = (Geometry)this.FindResource("ShareIcon")! } }, - new AppBarButton { Label = "Edit", Icon = new PathIcon { Data = (Geometry)this.FindResource("EditIcon")! } }, + new CommandBarButton { Label = "Search", Icon = new PathIcon { Data = (Geometry)this.FindResource("SearchIcon")! } }, + new CommandBarButton { Label = "Share", Icon = new PathIcon { Data = (Geometry)this.FindResource("ShareIcon")! } }, + new CommandBarButton { Label = "Edit", Icon = new PathIcon { Data = (Geometry)this.FindResource("EditIcon")! } }, }, SecondaryCommands = { - new AppBarButton { Label = "Delete", Icon = new PathIcon { Data = (Geometry)this.FindResource("DeleteIcon")! } } + new CommandBarButton { Label = "Delete", Icon = new PathIcon { Data = (Geometry)this.FindResource("DeleteIcon")! } } } }; } diff --git a/src/Avalonia.Controls/CommandBar/CommandBar.cs b/src/Avalonia.Controls/CommandBar/CommandBar.cs index 392ffb7ddc..d493bce304 100644 --- a/src/Avalonia.Controls/CommandBar/CommandBar.cs +++ b/src/Avalonia.Controls/CommandBar/CommandBar.cs @@ -516,7 +516,7 @@ namespace Avalonia.Controls int primaryNonSepCount = 0; foreach (var item in PrimaryCommands) - if (item is not AppBarSeparator) + if (item is not CommandBarSeparator) primaryNonSepCount++; const double overflowButtonWidth = 48; @@ -554,7 +554,7 @@ namespace Avalonia.Controls for (var i = 0; i < prioritized.Count; i++) { var idx = prioritized[i].Index; - if (PrimaryCommands[idx] is AppBarSeparator) + if (PrimaryCommands[idx] is CommandBarSeparator) visibleIndices.Add(idx); else if (nonSeparatorCount < maxItems) { @@ -596,8 +596,8 @@ namespace Avalonia.Controls private static int GetDynamicOverflowOrder(ICommandBarElement element) => element switch { - AppBarButton b => b.DynamicOverflowOrder, - AppBarToggleButton t => t.DynamicOverflowOrder, + CommandBarButton b => b.DynamicOverflowOrder, + CommandBarToggleButton t => t.DynamicOverflowOrder, _ => 0 }; @@ -615,9 +615,9 @@ namespace Avalonia.Controls { element.IsCompact = DefaultLabelPosition == CommandBarDefaultLabelPosition.Collapsed; - if (element is AppBarButton abb) + if (element is CommandBarButton abb) abb.LabelPosition = DefaultLabelPosition; - else if (element is AppBarToggleButton atb) + else if (element is CommandBarToggleButton atb) atb.LabelPosition = DefaultLabelPosition; } diff --git a/src/Avalonia.Controls/CommandBar/AppBarButton.cs b/src/Avalonia.Controls/CommandBar/CommandBarButton.cs similarity index 80% rename from src/Avalonia.Controls/CommandBar/AppBarButton.cs rename to src/Avalonia.Controls/CommandBar/CommandBarButton.cs index 9e251f77bf..776f4edc47 100644 --- a/src/Avalonia.Controls/CommandBar/AppBarButton.cs +++ b/src/Avalonia.Controls/CommandBar/CommandBarButton.cs @@ -3,49 +3,49 @@ namespace Avalonia.Controls /// /// A button for use in a . /// - public class AppBarButton : Button, ICommandBarElement + public class CommandBarButton : Button, ICommandBarElement { - static AppBarButton() + static CommandBarButton() { - ForegroundProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); - IconProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); + ForegroundProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); + IconProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); } /// /// Defines the property. /// public static readonly StyledProperty LabelProperty = - AvaloniaProperty.Register(nameof(Label)); + AvaloniaProperty.Register(nameof(Label)); /// /// Defines the property. /// public static readonly StyledProperty IconProperty = - AvaloniaProperty.Register(nameof(Icon)); + AvaloniaProperty.Register(nameof(Icon)); /// /// Defines the property. /// public static readonly StyledProperty IsCompactProperty = - AvaloniaProperty.Register(nameof(IsCompact)); + AvaloniaProperty.Register(nameof(IsCompact)); /// /// Defines the property. /// public static readonly StyledProperty DynamicOverflowOrderProperty = - AvaloniaProperty.Register(nameof(DynamicOverflowOrder)); + AvaloniaProperty.Register(nameof(DynamicOverflowOrder)); /// /// Defines the property. /// public static readonly StyledProperty LabelPositionProperty = - AvaloniaProperty.Register(nameof(LabelPosition), CommandBarDefaultLabelPosition.Bottom); + AvaloniaProperty.Register(nameof(LabelPosition), CommandBarDefaultLabelPosition.Bottom); /// /// Defines the property. /// public static readonly StyledProperty IsInOverflowProperty = - AvaloniaProperty.Register(nameof(IsInOverflow)); + AvaloniaProperty.Register(nameof(IsInOverflow)); /// /// Gets or sets the text label for the button. diff --git a/src/Avalonia.Controls/CommandBar/AppBarSeparator.cs b/src/Avalonia.Controls/CommandBar/CommandBarSeparator.cs similarity index 81% rename from src/Avalonia.Controls/CommandBar/AppBarSeparator.cs rename to src/Avalonia.Controls/CommandBar/CommandBarSeparator.cs index 53c114528f..16f3868441 100644 --- a/src/Avalonia.Controls/CommandBar/AppBarSeparator.cs +++ b/src/Avalonia.Controls/CommandBar/CommandBarSeparator.cs @@ -1,23 +1,21 @@ -using Avalonia.Controls.Primitives; - namespace Avalonia.Controls { /// /// A visual separator for use in a . /// - public class AppBarSeparator : TemplatedControl, ICommandBarElement + public class CommandBarSeparator : Separator, ICommandBarElement { /// /// Defines the property. /// public static readonly StyledProperty IsCompactProperty = - AvaloniaProperty.Register(nameof(IsCompact)); + AvaloniaProperty.Register(nameof(IsCompact)); /// /// Defines the property. /// public static readonly StyledProperty IsInOverflowProperty = - AvaloniaProperty.Register(nameof(IsInOverflow)); + AvaloniaProperty.Register(nameof(IsInOverflow)); /// /// Gets or sets whether the separator is in compact mode. diff --git a/src/Avalonia.Controls/CommandBar/AppBarToggleButton.cs b/src/Avalonia.Controls/CommandBar/CommandBarToggleButton.cs similarity index 79% rename from src/Avalonia.Controls/CommandBar/AppBarToggleButton.cs rename to src/Avalonia.Controls/CommandBar/CommandBarToggleButton.cs index 2476e3cb93..6d7ce4b61e 100644 --- a/src/Avalonia.Controls/CommandBar/AppBarToggleButton.cs +++ b/src/Avalonia.Controls/CommandBar/CommandBarToggleButton.cs @@ -5,49 +5,49 @@ namespace Avalonia.Controls /// /// A toggle button for use in a . /// - public class AppBarToggleButton : ToggleButton, ICommandBarElement + public class CommandBarToggleButton : ToggleButton, ICommandBarElement { - static AppBarToggleButton() + static CommandBarToggleButton() { - ForegroundProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); - IconProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); + ForegroundProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); + IconProperty.Changed.AddClassHandler((x, _) => x.UpdateIconForeground()); } /// /// Defines the property. /// public static readonly StyledProperty LabelProperty = - AvaloniaProperty.Register(nameof(Label)); + AvaloniaProperty.Register(nameof(Label)); /// /// Defines the property. /// public static readonly StyledProperty IconProperty = - AvaloniaProperty.Register(nameof(Icon)); + AvaloniaProperty.Register(nameof(Icon)); /// /// Defines the property. /// public static readonly StyledProperty IsCompactProperty = - AvaloniaProperty.Register(nameof(IsCompact)); + AvaloniaProperty.Register(nameof(IsCompact)); /// /// Defines the property. /// public static readonly StyledProperty DynamicOverflowOrderProperty = - AvaloniaProperty.Register(nameof(DynamicOverflowOrder)); + AvaloniaProperty.Register(nameof(DynamicOverflowOrder)); /// /// Defines the property. /// public static readonly StyledProperty LabelPositionProperty = - AvaloniaProperty.Register(nameof(LabelPosition), CommandBarDefaultLabelPosition.Bottom); + AvaloniaProperty.Register(nameof(LabelPosition), CommandBarDefaultLabelPosition.Bottom); /// /// Defines the property. /// public static readonly StyledProperty IsInOverflowProperty = - AvaloniaProperty.Register(nameof(IsInOverflow)); + AvaloniaProperty.Register(nameof(IsInOverflow)); /// /// Gets or sets the text label for the button. diff --git a/src/Avalonia.Themes.Fluent/Controls/CommandBar.xaml b/src/Avalonia.Themes.Fluent/Controls/CommandBar.xaml index 45ad19cb93..8692a6153f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CommandBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CommandBar.xaml @@ -5,29 +5,29 @@ - - + + - - - - + + + + - - - - - + + + + + - - + + - - + + @@ -108,8 +108,8 @@ - - + + @@ -195,8 +195,8 @@ - - + + diff --git a/src/Avalonia.Themes.Fluent/Controls/NavigationPage.xaml b/src/Avalonia.Themes.Fluent/Controls/NavigationPage.xaml index 45eee95081..3642f5fc22 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NavigationPage.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NavigationPage.xaml @@ -159,11 +159,11 @@ - - diff --git a/src/Avalonia.Themes.Simple/Controls/CommandBar.xaml b/src/Avalonia.Themes.Simple/Controls/CommandBar.xaml index f1384dc892..b489a20c4d 100644 --- a/src/Avalonia.Themes.Simple/Controls/CommandBar.xaml +++ b/src/Avalonia.Themes.Simple/Controls/CommandBar.xaml @@ -5,23 +5,23 @@ - - + + - - - - + + + + - - + + - - + + @@ -102,8 +102,8 @@ - - + + @@ -190,8 +190,8 @@ - - + + diff --git a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs index 8bd4401d10..eabb6e33c1 100644 --- a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs @@ -6,27 +6,27 @@ using Xunit; namespace Avalonia.Controls.UnitTests; -public class AppBarButtonTests : ScopedTestBase +public class CommandBarButtonTests : ScopedTestBase { [Fact] public void Label_DefaultIsNull() - => Assert.Null(new AppBarButton().Label); + => Assert.Null(new CommandBarButton().Label); [Fact] public void Label_RoundTrip() { - var btn = new AppBarButton { Label = "Save" }; + var btn = new CommandBarButton { Label = "Save" }; Assert.Equal("Save", btn.Label); } [Fact] public void Icon_DefaultIsNull() - => Assert.Null(new AppBarButton().Icon); + => Assert.Null(new CommandBarButton().Icon); [Fact] public void Icon_RoundTrip() { - var btn = new AppBarButton(); + var btn = new CommandBarButton(); var icon = new object(); btn.Icon = icon; Assert.Same(icon, btn.Icon); @@ -34,72 +34,72 @@ public class AppBarButtonTests : ScopedTestBase [Fact] public void IsCompact_DefaultIsFalse() - => Assert.False(new AppBarButton().IsCompact); + => Assert.False(new CommandBarButton().IsCompact); [Fact] public void IsCompact_RoundTrip() { - var btn = new AppBarButton { IsCompact = true }; + var btn = new CommandBarButton { IsCompact = true }; Assert.True(btn.IsCompact); } [Fact] public void DynamicOverflowOrder_DefaultIsZero() - => Assert.Equal(0, new AppBarButton().DynamicOverflowOrder); + => Assert.Equal(0, new CommandBarButton().DynamicOverflowOrder); [Fact] public void DynamicOverflowOrder_RoundTrip() { - var btn = new AppBarButton { DynamicOverflowOrder = 3 }; + var btn = new CommandBarButton { DynamicOverflowOrder = 3 }; Assert.Equal(3, btn.DynamicOverflowOrder); } [Fact] public void LabelPosition_DefaultIsBottom() - => Assert.Equal(CommandBarDefaultLabelPosition.Bottom, new AppBarButton().LabelPosition); + => Assert.Equal(CommandBarDefaultLabelPosition.Bottom, new CommandBarButton().LabelPosition); [Fact] public void LabelPosition_RoundTrip() { - var btn = new AppBarButton { LabelPosition = CommandBarDefaultLabelPosition.Right }; + var btn = new CommandBarButton { LabelPosition = CommandBarDefaultLabelPosition.Right }; Assert.Equal(CommandBarDefaultLabelPosition.Right, btn.LabelPosition); } [Fact] public void IsInOverflow_DefaultIsFalse() - => Assert.False(new AppBarButton().IsInOverflow); + => Assert.False(new CommandBarButton().IsInOverflow); [Fact] public void IsInOverflow_RoundTrip() { - var btn = new AppBarButton { IsInOverflow = true }; + var btn = new CommandBarButton { IsInOverflow = true }; Assert.True(btn.IsInOverflow); } [Fact] public void ImplementsICommandBarElement() - => Assert.IsAssignableFrom(new AppBarButton()); + => Assert.IsAssignableFrom(new CommandBarButton()); [Fact] public void ICommandBarElement_IsCompact_ReadWrite() { - ICommandBarElement elem = new AppBarButton(); + ICommandBarElement elem = new CommandBarButton(); elem.IsCompact = true; Assert.True(elem.IsCompact); } [Fact] public void Command_DefaultIsNull() - => Assert.Null(new AppBarButton().Command); + => Assert.Null(new CommandBarButton().Command); [Fact] public void CommandParameter_DefaultIsNull() - => Assert.Null(new AppBarButton().CommandParameter); + => Assert.Null(new CommandBarButton().CommandParameter); [Fact] public void Command_RoundTrip() { - var btn = new AppBarButton(); + var btn = new CommandBarButton(); var cmd = new DelegateCommand(_ => { }); btn.Command = cmd; Assert.Same(cmd, btn.Command); @@ -108,85 +108,85 @@ public class AppBarButtonTests : ScopedTestBase [Fact] public void CommandParameter_RoundTrip() { - var btn = new AppBarButton { CommandParameter = "param" }; + var btn = new CommandBarButton { CommandParameter = "param" }; Assert.Equal("param", btn.CommandParameter); } } -public class AppBarToggleButtonTests : ScopedTestBase +public class CommandBarToggleButtonTests : ScopedTestBase { [Fact] public void Label_DefaultIsNull() - => Assert.Null(new AppBarToggleButton().Label); + => Assert.Null(new CommandBarToggleButton().Label); [Fact] public void Label_RoundTrip() { - var btn = new AppBarToggleButton { Label = "Bold" }; + var btn = new CommandBarToggleButton { Label = "Bold" }; Assert.Equal("Bold", btn.Label); } [Fact] public void Icon_DefaultIsNull() - => Assert.Null(new AppBarToggleButton().Icon); + => Assert.Null(new CommandBarToggleButton().Icon); [Fact] public void IsCompact_DefaultIsFalse() - => Assert.False(new AppBarToggleButton().IsCompact); + => Assert.False(new CommandBarToggleButton().IsCompact); [Fact] public void IsCompact_RoundTrip() { - var btn = new AppBarToggleButton { IsCompact = true }; + var btn = new CommandBarToggleButton { IsCompact = true }; Assert.True(btn.IsCompact); } [Fact] public void DynamicOverflowOrder_DefaultIsZero() - => Assert.Equal(0, new AppBarToggleButton().DynamicOverflowOrder); + => Assert.Equal(0, new CommandBarToggleButton().DynamicOverflowOrder); [Fact] public void DynamicOverflowOrder_RoundTrip() { - var btn = new AppBarToggleButton { DynamicOverflowOrder = 5 }; + var btn = new CommandBarToggleButton { DynamicOverflowOrder = 5 }; Assert.Equal(5, btn.DynamicOverflowOrder); } [Fact] public void LabelPosition_DefaultIsBottom() - => Assert.Equal(CommandBarDefaultLabelPosition.Bottom, new AppBarToggleButton().LabelPosition); + => Assert.Equal(CommandBarDefaultLabelPosition.Bottom, new CommandBarToggleButton().LabelPosition); [Fact] public void LabelPosition_RoundTrip() { - var btn = new AppBarToggleButton { LabelPosition = CommandBarDefaultLabelPosition.Collapsed }; + var btn = new CommandBarToggleButton { LabelPosition = CommandBarDefaultLabelPosition.Collapsed }; Assert.Equal(CommandBarDefaultLabelPosition.Collapsed, btn.LabelPosition); } [Fact] public void IsInOverflow_DefaultIsFalse() - => Assert.False(new AppBarToggleButton().IsInOverflow); + => Assert.False(new CommandBarToggleButton().IsInOverflow); [Fact] public void ImplementsICommandBarElement() - => Assert.IsAssignableFrom(new AppBarToggleButton()); + => Assert.IsAssignableFrom(new CommandBarToggleButton()); [Fact] public void ICommandBarElement_IsCompact_ReadWrite() { - ICommandBarElement elem = new AppBarToggleButton(); + ICommandBarElement elem = new CommandBarToggleButton(); elem.IsCompact = true; Assert.True(elem.IsCompact); } [Fact] public void Command_DefaultIsNull() - => Assert.Null(new AppBarToggleButton().Command); + => Assert.Null(new CommandBarToggleButton().Command); [Fact] public void Command_RoundTrip() { - var btn = new AppBarToggleButton(); + var btn = new CommandBarToggleButton(); var cmd = new DelegateCommand(_ => { }); btn.Command = cmd; Assert.Same(cmd, btn.Command); @@ -195,43 +195,47 @@ public class AppBarToggleButtonTests : ScopedTestBase [Fact] public void CommandParameter_RoundTrip() { - var btn = new AppBarToggleButton { CommandParameter = 42 }; + var btn = new CommandBarToggleButton { CommandParameter = 42 }; Assert.Equal(42, btn.CommandParameter); } } -public class AppBarSeparatorTests : ScopedTestBase +public class CommandBarSeparatorTests : ScopedTestBase { [Fact] public void IsCompact_DefaultIsFalse() - => Assert.False(new AppBarSeparator().IsCompact); + => Assert.False(new CommandBarSeparator().IsCompact); [Fact] public void IsCompact_RoundTrip() { - var sep = new AppBarSeparator { IsCompact = true }; + var sep = new CommandBarSeparator { IsCompact = true }; Assert.True(sep.IsCompact); } [Fact] public void IsInOverflow_DefaultIsFalse() - => Assert.False(new AppBarSeparator().IsInOverflow); + => Assert.False(new CommandBarSeparator().IsInOverflow); [Fact] public void IsInOverflow_RoundTrip() { - var sep = new AppBarSeparator { IsInOverflow = true }; + var sep = new CommandBarSeparator { IsInOverflow = true }; Assert.True(sep.IsInOverflow); } [Fact] public void ImplementsICommandBarElement() - => Assert.IsAssignableFrom(new AppBarSeparator()); + => Assert.IsAssignableFrom(new CommandBarSeparator()); + + [Fact] + public void DerivesFromSeparator() + => Assert.IsAssignableFrom(new CommandBarSeparator()); [Fact] public void ICommandBarElement_IsCompact_ReadWrite() { - ICommandBarElement elem = new AppBarSeparator(); + ICommandBarElement elem = new CommandBarSeparator(); elem.IsCompact = true; Assert.True(elem.IsCompact); } @@ -517,7 +521,7 @@ public class CommandBarCollectionTests : ScopedTestBase public void PrimaryCommands_Added_AppearInVisiblePrimary_WhenDynamicOverflowDisabled() { var cb = new CommandBar(); - var btn = new AppBarButton { Label = "Save" }; + var btn = new CommandBarButton { Label = "Save" }; cb.PrimaryCommands!.Add(btn); Assert.Contains(btn, cb.VisiblePrimaryCommands); } @@ -530,7 +534,7 @@ public class CommandBarCollectionTests : ScopedTestBase ((INotifyCollectionChanged)cb.VisiblePrimaryCommands).CollectionChanged += (_, _) => notifications++; - cb.PrimaryCommands!.Add(new AppBarButton { Label = "Save" }); + cb.PrimaryCommands!.Add(new CommandBarButton { Label = "Save" }); Assert.Equal(2, notifications); } @@ -539,7 +543,7 @@ public class CommandBarCollectionTests : ScopedTestBase public void PrimaryCommands_Removed_DisappearsFromVisiblePrimary() { var cb = new CommandBar(); - var btn = new AppBarButton { Label = "Save" }; + var btn = new CommandBarButton { Label = "Save" }; cb.PrimaryCommands!.Add(btn); cb.PrimaryCommands!.Remove(btn); Assert.DoesNotContain(btn, cb.VisiblePrimaryCommands); @@ -549,7 +553,7 @@ public class CommandBarCollectionTests : ScopedTestBase public void SecondaryCommands_Added_AppearInOverflowItems() { var cb = new CommandBar(); - var btn = new AppBarButton { Label = "Settings" }; + var btn = new CommandBarButton { Label = "Settings" }; cb.SecondaryCommands!.Add(btn); Assert.Contains(btn, cb.OverflowItems); } @@ -562,7 +566,7 @@ public class CommandBarCollectionTests : ScopedTestBase ((INotifyCollectionChanged)cb.OverflowItems).CollectionChanged += (_, _) => notifications++; - cb.SecondaryCommands!.Add(new AppBarButton { Label = "Settings" }); + cb.SecondaryCommands!.Add(new CommandBarButton { Label = "Settings" }); Assert.Equal(2, notifications); } @@ -571,7 +575,7 @@ public class CommandBarCollectionTests : ScopedTestBase public void SecondaryCommands_Removed_DisappearsFromOverflowItems() { var cb = new CommandBar(); - var btn = new AppBarButton { Label = "Settings" }; + var btn = new CommandBarButton { Label = "Settings" }; cb.SecondaryCommands!.Add(btn); cb.SecondaryCommands!.Remove(btn); Assert.DoesNotContain(btn, cb.OverflowItems); @@ -581,7 +585,7 @@ public class CommandBarCollectionTests : ScopedTestBase public void HasSecondaryCommands_TrueWhenSecondaryAdded() { var cb = new CommandBar(); - cb.SecondaryCommands!.Add(new AppBarButton { Label = "Options" }); + cb.SecondaryCommands!.Add(new CommandBarButton { Label = "Options" }); Assert.True(cb.HasSecondaryCommands); } @@ -589,7 +593,7 @@ public class CommandBarCollectionTests : ScopedTestBase public void HasSecondaryCommands_FalseAfterSecondaryCleared() { var cb = new CommandBar(); - var btn = new AppBarButton { Label = "Options" }; + var btn = new CommandBarButton { Label = "Options" }; cb.SecondaryCommands!.Add(btn); cb.SecondaryCommands!.Remove(btn); Assert.False(cb.HasSecondaryCommands); @@ -599,8 +603,8 @@ public class CommandBarCollectionTests : ScopedTestBase public void OverflowItems_CountMatchesSecondaryCommandCount() { var cb = new CommandBar(); - cb.SecondaryCommands!.Add(new AppBarButton()); - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); Assert.Equal(2, cb.OverflowItems.Count); } @@ -608,8 +612,8 @@ public class CommandBarCollectionTests : ScopedTestBase public void VisiblePrimaryCommands_CountMatchesPrimary_WhenDynamicOverflowDisabled() { var cb = new CommandBar(); - cb.PrimaryCommands!.Add(new AppBarButton()); - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); Assert.Equal(2, cb.VisiblePrimaryCommands.Count); } @@ -617,9 +621,9 @@ public class CommandBarCollectionTests : ScopedTestBase public void MultiplePrimaryCommands_AllVisibleInOrder() { var cb = new CommandBar(); - var btn1 = new AppBarButton { Label = "A" }; - var btn2 = new AppBarButton { Label = "B" }; - var btn3 = new AppBarButton { Label = "C" }; + var btn1 = new CommandBarButton { Label = "A" }; + var btn2 = new CommandBarButton { Label = "B" }; + var btn3 = new CommandBarButton { Label = "C" }; cb.PrimaryCommands!.Add(btn1); cb.PrimaryCommands!.Add(btn2); cb.PrimaryCommands!.Add(btn3); @@ -627,19 +631,19 @@ public class CommandBarCollectionTests : ScopedTestBase } [Fact] - public void AppBarSeparator_CanBeAddedToPrimaryCommands() + public void CommandBarSeparator_CanBeAddedToPrimaryCommands() { var cb = new CommandBar(); - var sep = new AppBarSeparator(); + var sep = new CommandBarSeparator(); cb.PrimaryCommands!.Add(sep); Assert.Contains(sep, cb.VisiblePrimaryCommands); } [Fact] - public void AppBarToggleButton_CanBeAddedToPrimaryCommands() + public void CommandBarToggleButton_CanBeAddedToPrimaryCommands() { var cb = new CommandBar(); - var toggle = new AppBarToggleButton { Label = "Bold" }; + var toggle = new CommandBarToggleButton { Label = "Bold" }; cb.PrimaryCommands!.Add(toggle); Assert.Contains(toggle, cb.VisiblePrimaryCommands); } @@ -651,7 +655,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Collapsed_SetsIsCompactOnExistingPrimaryButton() { var cb = new CommandBar(); - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.PrimaryCommands!.Add(btn); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; @@ -663,7 +667,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Bottom_ClearsIsCompactOnPrimaryButton() { var cb = new CommandBar(); - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.PrimaryCommands!.Add(btn); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; @@ -676,7 +680,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Right_SetsLabelPositionOnPrimaryButton() { var cb = new CommandBar(); - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.PrimaryCommands!.Add(btn); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Right; @@ -688,7 +692,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Collapsed_SetsLabelPositionOnPrimaryButton() { var cb = new CommandBar(); - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.PrimaryCommands!.Add(btn); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; @@ -700,7 +704,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Collapsed_PropagatesIsCompactToToggleButton() { var cb = new CommandBar(); - var toggle = new AppBarToggleButton(); + var toggle = new CommandBarToggleButton(); cb.PrimaryCommands!.Add(toggle); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; @@ -713,7 +717,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Right_PropagatesLabelPositionToToggleButton() { var cb = new CommandBar(); - var toggle = new AppBarToggleButton(); + var toggle = new CommandBarToggleButton(); cb.PrimaryCommands!.Add(toggle); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Right; @@ -725,7 +729,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Collapsed_SetsIsCompactOnSeparator() { var cb = new CommandBar(); - var sep = new AppBarSeparator(); + var sep = new CommandBarSeparator(); cb.PrimaryCommands!.Add(sep); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; @@ -738,7 +742,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase { var cb = new CommandBar { DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed }; - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.PrimaryCommands!.Add(btn); Assert.True(btn.IsCompact); @@ -750,7 +754,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase { var cb = new CommandBar { DefaultLabelPosition = CommandBarDefaultLabelPosition.Right }; - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.PrimaryCommands!.Add(btn); Assert.Equal(CommandBarDefaultLabelPosition.Right, btn.LabelPosition); @@ -760,7 +764,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_Collapsed_AppliesToSecondaryCommands() { var cb = new CommandBar(); - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.SecondaryCommands!.Add(btn); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; @@ -772,7 +776,7 @@ public class CommandBarLabelPositionTests : ScopedTestBase public void DefaultLabelPosition_DoesNotClearLabelText() { var cb = new CommandBar(); - var btn = new AppBarButton { Label = "Save" }; + var btn = new CommandBarButton { Label = "Save" }; cb.PrimaryCommands!.Add(btn); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; @@ -801,7 +805,7 @@ public class CommandBarOverflowButtonTests : ScopedTestBase public void OverflowButtonVisibility_Auto_TrueWhenHasSecondaryCommands() { var cb = new CommandBar(); - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); Assert.True(cb.IsOverflowButtonVisible); } @@ -823,7 +827,7 @@ public class CommandBarOverflowButtonTests : ScopedTestBase public void OverflowButtonVisibility_Collapsed_RemainsFalseEvenWithSecondary() { var cb = new CommandBar { OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Collapsed }; - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); Assert.False(cb.IsOverflowButtonVisible); } @@ -831,7 +835,7 @@ public class CommandBarOverflowButtonTests : ScopedTestBase public void OverflowButtonVisibility_Auto_FalseAfterSecondaryRemoved() { var cb = new CommandBar(); - var btn = new AppBarButton(); + var btn = new CommandBarButton(); cb.SecondaryCommands!.Add(btn); Assert.True(cb.IsOverflowButtonVisible); @@ -863,9 +867,9 @@ public class CommandBarItemWidthTests : ScopedTestBase public void ItemWidthBottom_Controls_HowManyButtonsFit() { var cb = CreateWithWidth(300); - cb.SecondaryCommands!.Add(new AppBarButton()); // forces overflow button + cb.SecondaryCommands!.Add(new CommandBarButton()); // forces overflow button for (int i = 0; i < 4; i++) - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); cb.IsDynamicOverflowEnabled = true; Assert.Equal(3, cb.VisiblePrimaryCommands.Count); @@ -877,9 +881,9 @@ public class CommandBarItemWidthTests : ScopedTestBase { var cb = CreateWithWidth(300); cb.ItemWidthBottom = 35; - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); for (int i = 0; i < 4; i++) - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); cb.IsDynamicOverflowEnabled = true; Assert.Equal(4, cb.VisiblePrimaryCommands.Count); @@ -890,9 +894,9 @@ public class CommandBarItemWidthTests : ScopedTestBase { var cb = CreateWithWidth(300); cb.ItemWidthBottom = 260; - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); for (int i = 0; i < 3; i++) - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); cb.IsDynamicOverflowEnabled = true; Assert.Equal(1, cb.VisiblePrimaryCommands.Count); @@ -903,9 +907,9 @@ public class CommandBarItemWidthTests : ScopedTestBase { var cb = CreateWithWidth(300); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Right; - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); for (int i = 0; i < 4; i++) - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); cb.IsDynamicOverflowEnabled = true; Assert.Equal(2, cb.VisiblePrimaryCommands.Count); @@ -917,9 +921,9 @@ public class CommandBarItemWidthTests : ScopedTestBase var cb = CreateWithWidth(300); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Right; cb.ItemWidthRight = 252; // exactly 1 fits: 252/252=1 - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); for (int i = 0; i < 3; i++) - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); cb.IsDynamicOverflowEnabled = true; Assert.Equal(1, cb.VisiblePrimaryCommands.Count); @@ -930,9 +934,9 @@ public class CommandBarItemWidthTests : ScopedTestBase { var cb = CreateWithWidth(300); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Collapsed; - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); for (int i = 0; i < 4; i++) - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); cb.IsDynamicOverflowEnabled = true; Assert.Equal(4, cb.VisiblePrimaryCommands.Count); @@ -945,9 +949,9 @@ public class CommandBarItemWidthTests : ScopedTestBase cb.ItemWidthBottom = 70; cb.ItemWidthRight = 102; cb.ItemWidthCollapsed = 42; - cb.SecondaryCommands!.Add(new AppBarButton()); + cb.SecondaryCommands!.Add(new CommandBarButton()); for (int i = 0; i < 4; i++) - cb.PrimaryCommands!.Add(new AppBarButton()); + cb.PrimaryCommands!.Add(new CommandBarButton()); cb.DefaultLabelPosition = CommandBarDefaultLabelPosition.Bottom; cb.IsDynamicOverflowEnabled = true; diff --git a/tests/Avalonia.RenderTests/Controls/CommandBarTests.cs b/tests/Avalonia.RenderTests/Controls/CommandBarTests.cs index a518851027..ebef5ce419 100644 --- a/tests/Avalonia.RenderTests/Controls/CommandBarTests.cs +++ b/tests/Avalonia.RenderTests/Controls/CommandBarTests.cs @@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls Background = Brushes.LightGray, PrimaryCommands = { - new AppBarButton + new CommandBarButton { Label = "New", Icon = new Path @@ -49,7 +49,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls Stretch = Stretch.Uniform } }, - new AppBarButton + new CommandBarButton { Label = "Save", Icon = new Path @@ -61,8 +61,8 @@ namespace Avalonia.Direct2D1.RenderTests.Controls Stretch = Stretch.Uniform } }, - new AppBarSeparator(), - new AppBarToggleButton + new CommandBarSeparator(), + new CommandBarToggleButton { Label = "Bold", Icon = new Path @@ -98,7 +98,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Collapsed, PrimaryCommands = { - new AppBarButton + new CommandBarButton { Icon = new Path { @@ -109,7 +109,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls Stretch = Stretch.Uniform } }, - new AppBarButton + new CommandBarButton { Icon = new Path { @@ -120,8 +120,8 @@ namespace Avalonia.Direct2D1.RenderTests.Controls Stretch = Stretch.Uniform } }, - new AppBarSeparator(), - new AppBarToggleButton + new CommandBarSeparator(), + new CommandBarToggleButton { IsChecked = true, Icon = new Path diff --git a/tests/Avalonia.RenderTests/Controls/ContentPageTests.cs b/tests/Avalonia.RenderTests/Controls/ContentPageTests.cs index 07ba5b6505..1d3c30c8e5 100644 --- a/tests/Avalonia.RenderTests/Controls/ContentPageTests.cs +++ b/tests/Avalonia.RenderTests/Controls/ContentPageTests.cs @@ -71,7 +71,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls Background = Brushes.LightGray, PrimaryCommands = { - new AppBarButton + new CommandBarButton { Label = "Save", Icon = new Path @@ -83,8 +83,8 @@ namespace Avalonia.Direct2D1.RenderTests.Controls Stretch = Stretch.Uniform } }, - new AppBarSeparator(), - new AppBarToggleButton + new CommandBarSeparator(), + new CommandBarToggleButton { Label = "Bold", Icon = new Path @@ -105,7 +105,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Collapsed, PrimaryCommands = { - new AppBarButton + new CommandBarButton { Icon = new Path { From 3edf20b931ca242880a1f7ecec61c58e9ec85e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Mon, 30 Mar 2026 19:20:40 +0200 Subject: [PATCH 07/86] Apply optimizations in Avalonia Android (#20968) * Apply optimizations around Avalonia Android * More changes * Move benchmarks to run on Android * Remove Android Benchmark from build * More changes * Removed Android Benchmarks project --- .../Avalonia.Android/AndroidDispatcherImpl.cs | 21 ++++++---------- .../Avalonia.Android/AvaloniaAccessHelper.cs | 17 ++++++++++--- .../Platform/SkiaPlatform/TopLevelImpl.cs | 16 +++++++----- .../Helpers/AndroidKeyboardEventsHelper.cs | 25 +++++++++++++++++-- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidDispatcherImpl.cs b/src/Android/Avalonia.Android/AndroidDispatcherImpl.cs index 8ee5f2a8f0..ea23b64a42 100644 --- a/src/Android/Avalonia.Android/AndroidDispatcherImpl.cs +++ b/src/Android/Avalonia.Android/AndroidDispatcherImpl.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Threading; using Android.OS; using Avalonia.Controls.Documents; using Avalonia.Threading; @@ -19,8 +20,7 @@ namespace Avalonia.Android private readonly Runnable _timerSignaler; private readonly Runnable _wakeupSignaler; private readonly MessageQueue _queue; - private readonly object _lock = new(); - private bool _signaled; + private int _signaled; private bool _backgroundProcessingRequested; @@ -46,8 +46,7 @@ namespace Avalonia.Android public event Action? Signaled; private void OnSignaled() { - lock (_lock) - _signaled = false; + Interlocked.Exchange(ref _signaled, 0); Signaled?.Invoke(); } @@ -68,13 +67,11 @@ namespace Avalonia.Android public void Signal() { - lock (_lock) + if (Interlocked.CompareExchange(ref _signaled, 1, 0) != 0) { - if(_signaled) - return; - _signaled = true; - _handler.Post(_signaler); + return; } + _handler.Post(_signaler); } readonly Stopwatch _clock = Stopwatch.StartNew(); @@ -133,11 +130,9 @@ namespace Avalonia.Android // "background" jobs not being processed // So we need to examine the queue state to prevent that scenario - lock (_lock) + if (Volatile.Read(ref _signaled) != 0) { - // There are higher priority jobs enqueued, we'll be called again - if (_signaled) - return; + return; } if (CanQueryPendingInput) diff --git a/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs b/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs index ca06998dbc..6d5128ece2 100644 --- a/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs +++ b/src/Android/Avalonia.Android/AvaloniaAccessHelper.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; + using Android.OS; using AndroidX.Core.View.Accessibility; using AndroidX.CustomView.Widget; @@ -134,9 +134,18 @@ namespace Avalonia.Android protected override bool OnPerformActionForVirtualView(int virtualViewId, int action, Bundle? arguments) { - return (GetNodeInfoProvidersFromVirtualViewId(virtualViewId) ?? []) - .Select(x => TryPerformNodeAction(x, action, arguments)) - .Aggregate(false, (a, b) => a | b); + var providers = GetNodeInfoProvidersFromVirtualViewId(virtualViewId); + if (providers == null) + { + return false; + } + + var result = false; + foreach (var provider in providers) + { + result |= TryPerformNodeAction(provider, action, arguments); + } + return result; } private static bool TryPerformNodeAction(INodeInfoProvider nodeInfoProvider, int action, Bundle? arguments) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 20284906be..da128cec96 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -4,6 +4,7 @@ using Android.App; using Android.Content; using Android.Graphics; using Android.Graphics.Drawables; +using Android.OS; using Android.Runtime; using Android.Views; using AndroidX.AppCompat.App; @@ -144,6 +145,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly TopLevelImpl _tl; private Size _oldSize; private double _oldScaling; + private Paint? _clearPaint; public SurfaceViewImpl(Context context, TopLevelImpl tl, bool placeOnTop) : base(context) { @@ -159,11 +161,13 @@ namespace Avalonia.Android.Platform.SkiaPlatform // can be seen below, but it does not. if (OperatingSystem.IsAndroidVersionAtLeast(29)) { - // Android 10+ does this (BlendMode was new) - var paint = new Paint(); - paint.SetColor(0); - paint.BlendMode = BlendMode.Clear; - canvas.DrawRect(0, 0, Width, Height, paint); + if (_clearPaint == null) + { + _clearPaint = new Paint(); + _clearPaint.SetColor(0); + _clearPaint.BlendMode = BlendMode.Clear; + } + canvas.DrawRect(0, 0, Width, Height, _clearPaint); } else { @@ -384,7 +388,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { if(Input != null) { - var args = new RawTextInputEventArgs(AndroidKeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, InputRoot!, text); + var args = new RawTextInputEventArgs(AndroidKeyboardDevice.Instance!, (ulong)SystemClock.UptimeMillis(), InputRoot!, text); Input(args); } diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 03be7c2153..59b1bc9899 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -10,6 +10,18 @@ namespace Avalonia.Android.Platform.Specific.Helpers { internal class AndroidKeyboardEventsHelper : IDisposable where TView : TopLevelImpl { + private static readonly string[] s_asciiStringCache = InitAsciiStringCache(); + + private static string[] InitAsciiStringCache() + { + var cache = new string[128]; + for (int i = 0; i < 128; i++) + { + cache[i] = ((char)i).ToString(); + } + return cache; + } + private readonly TView _view; public bool HandleEvents { get; set; } @@ -79,7 +91,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers AndroidKeyboardDevice.Instance!, Convert.ToUInt64(e.EventTime), inputRoot, - unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString() + unicodeTextInput ?? CharToString(e.UnicodeChar) ); _view.Input?.Invoke(rawTextEvent); @@ -107,6 +119,15 @@ namespace Avalonia.Android.Platform.Specific.Helpers return rv; } + private static string CharToString(int unicodeChar) + { + if (unicodeChar >= 0 && unicodeChar < s_asciiStringCache.Length) + { + return s_asciiStringCache[unicodeChar]; + } + return char.ConvertFromUtf32(unicodeChar); + } + private static string? GetKeySymbol(int unicodeChar, PhysicalKey physicalKey) { // Handle a very limited set of control characters so that we're consistent with other platforms @@ -126,7 +147,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers if (unicodeChar <= 0x7F) { var asciiChar = (char)unicodeChar; - return KeySymbolHelper.IsAllowedAsciiKeySymbol(asciiChar) ? asciiChar.ToString() : null; + return KeySymbolHelper.IsAllowedAsciiKeySymbol(asciiChar) ? s_asciiStringCache[asciiChar] : null; } return char.ConvertFromUtf32(unicodeChar); } From 118a1174e215d52290a28500734790ac6a95ee09 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 30 Mar 2026 19:36:38 +0000 Subject: [PATCH 08/86] Fix Mouse pointer triggering focus change on pointer release. (#21009) * add tests * explicitly check which pointer events trigger focus change * add test checking for change in focus while mouse is pressed. --- src/Avalonia.Base/Input/FocusManager.cs | 14 ++- .../Input/InputElement_Focus.cs | 50 ++++----- .../Input/MouseDeviceTests.cs | 103 ++++++++++++++++-- .../Input/PointerTestsBase.cs | 2 - .../Input/TouchDeviceTests.cs | 67 ++++++++++-- 5 files changed, 186 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs index a273ff6d89..651210fc2b 100644 --- a/src/Avalonia.Base/Input/FocusManager.cs +++ b/src/Avalonia.Base/Input/FocusManager.cs @@ -111,7 +111,7 @@ namespace Avalonia.Input scope.ClearValue(FocusedElementProperty); } - if (Current == removedElement) + if (Current == removedElement) Focus(null); } @@ -158,7 +158,7 @@ namespace Avalonia.Input /// internal static FocusManager? GetFocusManager(IInputElement? element) { - + // Element might not be a visual, and not attached to the root. // But IFocusManager is always expected to be a FocusManager. return (FocusManager?)(element as Visual)?.GetInputRoot()?.FocusManager @@ -188,8 +188,12 @@ namespace Avalonia.Input { if (CanFocus(e)) { - if (ev.Pointer.Type == PointerType.Mouse || ev is PointerReleasedEventArgs) - return true; + return ev switch + { + PointerReleasedEventArgs releasedEventArgs when releasedEventArgs.Pointer.Type != PointerType.Mouse => true, + PointerPressedEventArgs pressedEventArgs when pressedEventArgs.Pointer.Type == PointerType.Mouse => true, + _ => false, + }; } return false; @@ -229,7 +233,7 @@ namespace Avalonia.Input var root = v.PresentationSource?.InputRoot.FocusRoot as Visual; while (root is IHostedVisualTreeRoot hosted && - hosted.Host?.PresentationSource?.InputRoot.FocusRoot is {} parentRoot) + hosted.Host?.PresentationSource?.InputRoot.FocusRoot is { } parentRoot) { root = parentRoot; } diff --git a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs index a09eccc1a3..45d665b591 100644 --- a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs +++ b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs @@ -24,7 +24,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.Same(target, root.FocusManager.GetFocusedElement()); } } - + [Fact] public void Invisible_Controls_Should_Not_Receive_Focus() { @@ -34,20 +34,20 @@ namespace Avalonia.Base.UnitTests.Input { var root = new TestRoot { - Child = target = new Button() { IsVisible = false} + Child = target = new Button() { IsVisible = false } }; - + Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); - + Assert.False(target.IsFocused); Assert.False(target.IsKeyboardFocusWithin); Assert.Null(root.FocusManager.GetFocusedElement()); } } - + [Fact] public void Effectively_Invisible_Controls_Should_Not_Receive_Focus() { @@ -64,11 +64,11 @@ namespace Avalonia.Base.UnitTests.Input Children = { target } } }; - + Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); - + Assert.False(target.IsFocused); Assert.False(target.IsKeyboardFocusWithin); @@ -87,7 +87,7 @@ namespace Avalonia.Base.UnitTests.Input var root = new TestRoot { Child = new StackPanel - { + { Children = { (first = new Button()), @@ -365,7 +365,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(target2.Classes.Contains(":focus-visible")); } } - + [Fact] public void Control_FocusWithin_PseudoClass_Should_Be_Applied() { @@ -398,7 +398,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(root.IsKeyboardFocusWithin); } } - + [Fact] public void Control_FocusWithin_PseudoClass_Should_Be_Applied_and_Removed() { @@ -419,7 +419,7 @@ namespace Avalonia.Base.UnitTests.Input } } }; - + target1.ApplyTemplate(); target2.ApplyTemplate(); @@ -433,9 +433,9 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(root.Child.IsKeyboardFocusWithin); Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.IsKeyboardFocusWithin); - + target2.Focus(); - + Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.IsKeyboardFocusWithin); @@ -445,7 +445,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(root.Child.IsKeyboardFocusWithin); Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.IsKeyboardFocusWithin); - + Assert.True(target2.IsFocused); Assert.True(target2.Classes.Contains(":focus-within")); Assert.True(target2.IsKeyboardFocusWithin); @@ -453,7 +453,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(panel2.IsKeyboardFocusWithin); } } - + [Fact] public void Control_FocusWithin_Pseudoclass_Should_Be_Removed_When_Removed_From_Tree() { @@ -487,11 +487,11 @@ namespace Avalonia.Base.UnitTests.Input var keyboardDevice = KeyboardDevice.Instance!; Assert.Equal(keyboardDevice.FocusedElement, target1); - + root.Child = null; - + Assert.Null(keyboardDevice.FocusedElement); - + Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.IsKeyboardFocusWithin); @@ -499,7 +499,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(root.IsKeyboardFocusWithin); } } - + [Fact] public void Control_FocusWithin_Pseudoclass_Should_Be_Removed_Focus_Moves_To_Different_Root() { @@ -507,7 +507,7 @@ namespace Avalonia.Base.UnitTests.Input { var target1 = new Decorator { Focusable = true }; var target2 = new Decorator { Focusable = true }; - + var root1 = new TestRoot { Child = new StackPanel @@ -518,7 +518,7 @@ namespace Avalonia.Base.UnitTests.Input } } }; - + var root2 = new TestRoot { Child = new StackPanel @@ -543,9 +543,9 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(root1.IsKeyboardFocusWithin); Assert.Equal(KeyboardDevice.Instance!.FocusedElement, target1); - + target2.Focus(); - + Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.IsKeyboardFocusWithin); @@ -553,7 +553,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(root1.Child.IsKeyboardFocusWithin); Assert.False(root1.Classes.Contains(":focus-within")); Assert.False(root1.IsKeyboardFocusWithin); - + Assert.True(target2.IsFocused); Assert.True(target2.Classes.Contains(":focus-within")); Assert.True(target2.IsKeyboardFocusWithin); @@ -1031,7 +1031,7 @@ namespace Avalonia.Base.UnitTests.Input [XYFocus.UpProperty] = target3, [XYFocus.DownProperty] = target4, }; - var container = new Canvas + var container = new Canvas { Children = { diff --git a/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs index 6380f71935..1ec6ec2be5 100644 --- a/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs @@ -4,7 +4,6 @@ using Avalonia.Input.Raw; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Threading; using Avalonia.UnitTests; using Moq; using Xunit; @@ -18,7 +17,7 @@ namespace Avalonia.Base.UnitTests.Input { using var scope = AvaloniaLocator.EnterScope(); var settingsMock = new Mock(); - + AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(settingsMock.Object); @@ -32,7 +31,7 @@ namespace Avalonia.Base.UnitTests.Input var control = new Control(); var root = CreateInputRoot(impl.Object, control, renderer.Object); - + MouseButton button = default; root.PointerReleased += (s, e) => button = e.InitialPressMouseButton; @@ -50,10 +49,10 @@ namespace Avalonia.Base.UnitTests.Input impl.Object.Input!(up); Assert.Equal(MouseButton.Left, button); - + impl.Object.Input!(up); - Assert.Equal(MouseButton.None, button); + Assert.Equal(MouseButton.None, button); } [Fact] @@ -85,7 +84,7 @@ namespace Avalonia.Base.UnitTests.Input impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); Assert.NotNull(result); - + result.Capture(control); Assert.Same(control, result.Captured); @@ -115,8 +114,8 @@ namespace Avalonia.Base.UnitTests.Input }) } }, renderer.Object); - - + + Point? result = null; root.PointerMoved += (_, a) => { @@ -128,5 +127,93 @@ namespace Avalonia.Base.UnitTests.Input Assert.Equal(new Point(1, 11), result); } + + [Fact] + public void Mouse_Pointer_Should_Set_Focus_On_Pointer_Pressed() + { + using var scope = AvaloniaLocator.EnterScope(); + var settingsMock = new Mock(); + + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(settingsMock.Object); + + using var app = UnitTestApplication.Start( + TestServices.RealFocus); + + var renderer = new Mock(); + var impl = CreateTopLevelImplMock(); + + var control = new Button() + { + Focusable = true + }; + var root = CreateInputRoot(impl.Object, control, renderer.Object); + + var device = new MouseDevice(); + + var down = CreateRawPointerArgs(device, root, RawPointerEventType.LeftButtonDown); + var up = CreateRawPointerArgs(device, root, RawPointerEventType.LeftButtonUp); + + SetHit(renderer, control); + + Assert.False(control.IsFocused); + + impl.Object.Input!(down); + + Assert.True(control.IsFocused); + impl.Object.Input!(up); + + Assert.True(control.IsFocused); + } + + [Fact] + public void Control_Should_Not_Gain_Focus_On_Mouse_Release() + { + using var scope = AvaloniaLocator.EnterScope(); + var settingsMock = new Mock(); + + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(settingsMock.Object); + + using var app = UnitTestApplication.Start( + TestServices.RealFocus); + + var renderer = new Mock(); + var impl = CreateTopLevelImplMock(); + + var control1 = new Button() + { + Focusable = true + }; + + var control2 = new Button() + { + Focusable = true + }; + var stack = new StackPanel() + { + Children = { control1, control2 } + }; + var root = CreateInputRoot(impl.Object, stack, renderer.Object); + + var device = new MouseDevice(); + + var down = CreateRawPointerArgs(device, root, RawPointerEventType.LeftButtonDown); + var up = CreateRawPointerArgs(device, root, RawPointerEventType.LeftButtonUp); + + SetHit(renderer, control1); + + Assert.False(control1.IsFocused); + + impl.Object.Input!(down); + + Assert.True(control1.IsFocused); + + control2.Focus(); + + impl.Object.Input!(up); + + Assert.False(control1.IsFocused); + } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs index c75ff56d82..1675dc9e62 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs @@ -7,9 +7,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Rendering.Composition; using Avalonia.UnitTests; -using Avalonia.VisualTree; using Moq; namespace Avalonia.Base.UnitTests.Input; diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index 97f83b1a69..600856c643 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -1,14 +1,16 @@ using System; +using Avalonia.Base.UnitTests.Input; +using Avalonia.Controls; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Threading; +using Avalonia.Rendering; using Avalonia.UnitTests; using Moq; using Xunit; namespace Avalonia.Input.UnitTests { - public class TouchDeviceTests + public class TouchDeviceTests : PointerTestsBase { [Fact] public void Tapped_Event_Is_Fired_With_Touch() @@ -141,6 +143,36 @@ namespace Avalonia.Input.UnitTests Assert.Equal(0, doubleTappedExecutedTimes); } + [Fact] + public void Touch_Pointer_Should_Set_Focus_On_Pointer_Released() + { + using var scope = AvaloniaLocator.EnterScope(); + using var app = UnitTestApplication.Start( + TestServices.RealFocus); + + var impl = CreateTopLevelImplMock(); + + var renderer = new Mock(); + var root = new TestTopLevel(impl.Object) + { + HitTesterOverride = renderer.Object, + }; + var host = root.TopLevelHost; + + host.Focusable = true; + var touchDevice = new TouchDevice(); + var inputManager = InputManager.Instance!; + + Assert.False(host.IsFocused); + + Press(InputManager.Instance!, touchDevice, root.InputRoot); + + Assert.False(host.IsFocused); + Release(InputManager.Instance!, touchDevice, root.InputRoot); + + Assert.True(host.IsFocused); + } + [Fact] public void Click_Counting_Should_Work_Correctly_With_Few_Touch_Contacts() { @@ -241,14 +273,12 @@ namespace Avalonia.Input.UnitTests private static void TapOnce(IInputManager inputManager, TouchDevice device, IInputRoot root, ulong timestamp = 0, long touchPointId = 0) { - inputManager.ProcessInput(new RawPointerEventArgs(device, timestamp, - root, - RawPointerEventType.TouchBegin, - new Point(0, 0), - RawInputModifiers.None) - { - RawPointerId = touchPointId - }); + Press(inputManager, device, root, timestamp, touchPointId); + Release(inputManager, device, root, timestamp, touchPointId); + } + + private static void Release(IInputManager inputManager, TouchDevice device, IInputRoot root, ulong timestamp = 0, long touchPointId = 0) + { inputManager.ProcessInput(new RawPointerEventArgs(device, timestamp, root, RawPointerEventType.TouchEnd, @@ -258,5 +288,22 @@ namespace Avalonia.Input.UnitTests RawPointerId = touchPointId }); } + + private static void Press(IInputManager inputManager, TouchDevice device, IInputRoot root, ulong timestamp = 0, long touchPointId = 0) + { + inputManager.ProcessInput(new RawPointerEventArgs(device, timestamp, + root, + RawPointerEventType.TouchBegin, + new Point(0, 0), + RawInputModifiers.None) + { + RawPointerId = touchPointId + }); + } + + private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl) + { + + } } } From 5ba86721dedc565f88037fbcec79ee2bfa32403e Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 30 Mar 2026 23:04:58 +0200 Subject: [PATCH 09/86] Change DoDragDropAsync trigger event to PointerPressedEventArgs (#20988) * Change DnD trigger event to PointerPressedEventArgs * Update API suppressions * Switch IPlatformDragSource to a private API * Update API suppressions --- api/Avalonia.nupkg.xml | 300 ++++++++++-------- src/Avalonia.Base/Input/DragDrop.cs | 2 +- .../Input/Platform/IPlatformDragSource.cs | 4 +- .../Platform/InProcessDragSource.cs | 2 +- .../AvaloniaNativeDragSource.cs | 2 +- src/Windows/Avalonia.Win32/DragSource.cs | 2 +- 6 files changed, 174 insertions(+), 138 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index a63ac3baae..cbaf782d24 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -247,6 +247,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Controls.AppBarButton + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarSeparator + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarToggleButton + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0001 T:Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetimeOptions @@ -727,6 +745,24 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Controls.AppBarButton + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarSeparator + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0001 + T:Avalonia.Controls.AppBarToggleButton + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0001 T:Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetimeOptions @@ -1117,6 +1153,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.DragDrop.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.DragEventArgs.#ctor(Avalonia.Interactivity.RoutedEvent{Avalonia.Input.DragEventArgs},Avalonia.Input.IDataObject,Avalonia.Interactivity.Interactive,Avalonia.Point,Avalonia.Input.KeyModifiers) @@ -1417,6 +1459,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.Raw.RawDragEvent.#ctor(Avalonia.Input.Raw.IDragDropDevice,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IInputRoot,Avalonia.Point,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers) @@ -1885,6 +1933,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.NavigationPage.IsBackButtonEffectivelyVisibleProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + F:Avalonia.Controls.PipsPager.NextButtonStyleProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + F:Avalonia.Controls.PipsPager.PreviousButtonStyleProperty + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.Primitives.FlyoutBase.IsOpenProperty @@ -2119,12 +2185,42 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.NavigationPage.get_IsBackButtonEffectivelyVisible + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.PageSelectionChangedEventArgs.#ctor(Avalonia.Controls.Page,Avalonia.Controls.Page) baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.PipsPager.get_NextButtonStyle + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.PipsPager.get_PreviousButtonStyle + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.PipsPager.set_NextButtonStyle(Avalonia.Styling.ControlTheme) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.PipsPager.set_PreviousButtonStyle(Avalonia.Styling.ControlTheme) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Platform.DefaultMenuInteractionHandler.GotFocus(System.Object,Avalonia.Input.GotFocusEventArgs) @@ -2815,6 +2911,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.DragDrop.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.DragEventArgs.#ctor(Avalonia.Interactivity.RoutedEvent{Avalonia.Input.DragEventArgs},Avalonia.Input.IDataObject,Avalonia.Interactivity.Interactive,Avalonia.Point,Avalonia.Input.KeyModifiers) @@ -3115,6 +3217,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Input.Raw.RawDragEvent.#ctor(Avalonia.Input.Raw.IDragDropDevice,Avalonia.Input.Raw.RawDragEventType,Avalonia.Input.IInputRoot,Avalonia.Point,Avalonia.Input.IDataObject,Avalonia.Input.DragDropEffects,Avalonia.Input.RawInputModifiers) @@ -3583,6 +3691,24 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + F:Avalonia.Controls.NavigationPage.IsBackButtonEffectivelyVisibleProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + F:Avalonia.Controls.PipsPager.NextButtonStyleProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + F:Avalonia.Controls.PipsPager.PreviousButtonStyleProperty + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Controls.Primitives.FlyoutBase.IsOpenProperty @@ -3817,12 +3943,42 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.NavigationPage.get_IsBackButtonEffectivelyVisible + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.PageSelectionChangedEventArgs.#ctor(Avalonia.Controls.Page,Avalonia.Controls.Page) baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.PipsPager.get_NextButtonStyle + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.PipsPager.get_PreviousButtonStyle + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.PipsPager.set_NextButtonStyle(Avalonia.Styling.ControlTheme) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.PipsPager.set_PreviousButtonStyle(Avalonia.Styling.ControlTheme) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Controls.Platform.DefaultMenuInteractionHandler.GotFocus(System.Object,Avalonia.Input.GotFocusEventArgs) @@ -4471,6 +4627,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0006 + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerPressedEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0006 M:Avalonia.Platform.ICursorFactory.CreateCursor(Avalonia.Media.Imaging.Bitmap,Avalonia.PixelPoint) @@ -4819,6 +4981,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0006 + M:Avalonia.Input.Platform.IPlatformDragSource.DoDragDropAsync(Avalonia.Input.PointerPressedEventArgs,Avalonia.Input.IDataTransfer,Avalonia.Input.DragDropEffects) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0006 M:Avalonia.Platform.ICursorFactory.CreateCursor(Avalonia.Media.Imaging.Bitmap,Avalonia.PixelPoint) @@ -5725,136 +5893,4 @@ baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll - - CP0002 - F:Avalonia.Controls.NavigationPage.IsBackButtonEffectivelyVisibleProperty - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.NavigationPage.get_IsBackButtonEffectivelyVisible - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - F:Avalonia.Controls.PipsPager.PreviousButtonStyleProperty - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - F:Avalonia.Controls.PipsPager.NextButtonStyleProperty - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.get_PreviousButtonStyle - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.set_PreviousButtonStyle(Avalonia.Styling.ControlTheme) - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.get_NextButtonStyle - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.set_NextButtonStyle(Avalonia.Styling.ControlTheme) - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0002 - F:Avalonia.Controls.NavigationPage.IsBackButtonEffectivelyVisibleProperty - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.NavigationPage.get_IsBackButtonEffectivelyVisible - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0002 - F:Avalonia.Controls.PipsPager.PreviousButtonStyleProperty - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0002 - F:Avalonia.Controls.PipsPager.NextButtonStyleProperty - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.get_PreviousButtonStyle - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.set_PreviousButtonStyle(Avalonia.Styling.ControlTheme) - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.get_NextButtonStyle - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0002 - M:Avalonia.Controls.PipsPager.set_NextButtonStyle(Avalonia.Styling.ControlTheme) - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0001 - T:Avalonia.Controls.AppBarButton - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0001 - T:Avalonia.Controls.AppBarSeparator - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0001 - T:Avalonia.Controls.AppBarToggleButton - baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll - current/Avalonia/lib/net10.0/Avalonia.Controls.dll - - - CP0001 - T:Avalonia.Controls.AppBarButton - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0001 - T:Avalonia.Controls.AppBarSeparator - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - - - CP0001 - T:Avalonia.Controls.AppBarToggleButton - baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll - current/Avalonia/lib/net8.0/Avalonia.Controls.dll - diff --git a/src/Avalonia.Base/Input/DragDrop.cs b/src/Avalonia.Base/Input/DragDrop.cs index 33f538c443..fa94e37b8c 100644 --- a/src/Avalonia.Base/Input/DragDrop.cs +++ b/src/Avalonia.Base/Input/DragDrop.cs @@ -127,7 +127,7 @@ namespace Avalonia.Input /// /// public static Task DoDragDropAsync( - PointerEventArgs triggerEvent, + PointerPressedEventArgs triggerEvent, IDataTransfer dataTransfer, DragDropEffects allowedEffects) { diff --git a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs index 4ad36576ca..d5835d96b5 100644 --- a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs +++ b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs @@ -3,11 +3,11 @@ using Avalonia.Metadata; namespace Avalonia.Input.Platform { - [NotClientImplementable] + [PrivateApi] public interface IPlatformDragSource { Task DoDragDropAsync( - PointerEventArgs triggerEvent, + PointerPressedEventArgs triggerEvent, IDataTransfer dataTransfer, DragDropEffects allowedEffects); } diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 1a7b719499..6a8b79eab6 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -31,7 +31,7 @@ namespace Avalonia.Platform } public async Task DoDragDropAsync( - PointerEventArgs triggerEvent, + PointerPressedEventArgs triggerEvent, IDataTransfer dataTransfer, DragDropEffects allowedEffects) { diff --git a/src/Avalonia.Native/AvaloniaNativeDragSource.cs b/src/Avalonia.Native/AvaloniaNativeDragSource.cs index 662d79d1b8..5927397ab5 100644 --- a/src/Avalonia.Native/AvaloniaNativeDragSource.cs +++ b/src/Avalonia.Native/AvaloniaNativeDragSource.cs @@ -33,7 +33,7 @@ namespace Avalonia.Native } public Task DoDragDropAsync( - PointerEventArgs triggerEvent, + PointerPressedEventArgs triggerEvent, IDataTransfer dataTransfer, DragDropEffects allowedEffects) { diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs index f1cdfa440a..9ca295a904 100644 --- a/src/Windows/Avalonia.Win32/DragSource.cs +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -10,7 +10,7 @@ namespace Avalonia.Win32 internal sealed class DragSource : IPlatformDragSource { public Task DoDragDropAsync( - PointerEventArgs triggerEvent, + PointerPressedEventArgs triggerEvent, IDataTransfer dataTransfer, DragDropEffects allowedEffects) { From 02d17c78636decb76d5eb3058713657c0f305e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Mon, 30 Mar 2026 22:32:22 +0200 Subject: [PATCH 10/86] Apply optimizations in Avalonia iOS (#20969) * Apply optimizations on Avalonia iOS * Updated benchmarks * More changes * Removed iOS Benchmarks project --- src/iOS/Avalonia.iOS/DispatcherImpl.cs | 23 +++++++--------------- src/iOS/Avalonia.iOS/InputHandler.cs | 4 ++-- src/iOS/Avalonia.iOS/TextInputResponder.cs | 3 ++- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/iOS/Avalonia.iOS/DispatcherImpl.cs b/src/iOS/Avalonia.iOS/DispatcherImpl.cs index b39ba1a85a..1bf4503af8 100644 --- a/src/iOS/Avalonia.iOS/DispatcherImpl.cs +++ b/src/iOS/Avalonia.iOS/DispatcherImpl.cs @@ -22,7 +22,8 @@ internal class DispatcherImpl : IDispatcherImplWithExplicitBackgroundProcessing private readonly IntPtr _mainLoop; private readonly IntPtr _mainQueue; private Thread? _loopThread; - private bool _backgroundProcessingRequested, _signaled; + private bool _backgroundProcessingRequested; + private int _signaled; private unsafe DispatcherImpl() { @@ -60,15 +61,12 @@ internal class DispatcherImpl : IDispatcherImplWithExplicitBackgroundProcessing public unsafe void Signal() { - lock (_sync) + if (Interlocked.CompareExchange(ref _signaled, 1, 0) != 0) { - if (_signaled) - return; - _signaled = true; - - Interop.dispatch_async_f(_mainQueue, IntPtr.Zero, &CheckSignaled); - Interop.CFRunLoopWakeUp(_mainLoop); + return; } + Interop.dispatch_async_f(_mainQueue, IntPtr.Zero, &CheckSignaled); + Interop.CFRunLoopWakeUp(_mainLoop); } public void UpdateTimer(long? dueTimeInMs) @@ -91,14 +89,7 @@ internal class DispatcherImpl : IDispatcherImplWithExplicitBackgroundProcessing private void CheckSignaled() { - bool signaled; - lock (_sync) - { - signaled = _signaled; - _signaled = false; - } - - if (signaled) + if (Interlocked.Exchange(ref _signaled, 0) != 0) { Signaled?.Invoke(); } diff --git a/src/iOS/Avalonia.iOS/InputHandler.cs b/src/iOS/Avalonia.iOS/InputHandler.cs index bd66d5a51e..06542cbb0d 100644 --- a/src/iOS/Avalonia.iOS/InputHandler.cs +++ b/src/iOS/Avalonia.iOS/InputHandler.cs @@ -308,7 +308,7 @@ internal sealed class InputHandler _tl.Input?.Invoke(new RawMouseWheelEventArgs( _mouseDevice, - (ulong)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()), + (ulong)Environment.TickCount64, Root, _cachedScrollLocation ?? new Point(0, 0), new Vector(deltaX, deltaY), @@ -372,7 +372,7 @@ internal sealed class InputHandler // until the inertia stops. _tl.Input?.Invoke(new RawMouseWheelEventArgs( _mouseDevice, - (ulong)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()), + (ulong)Environment.TickCount64, Root, _cachedScrollLocation.Value, new Vector(_momentumVelocityX, _momentumVelocityY), diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs index 678fac8766..b2c1d1700c 100644 --- a/src/iOS/Avalonia.iOS/TextInputResponder.cs +++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs @@ -84,6 +84,7 @@ partial class AvaloniaView private int _inSurroundingTextUpdateEvent; private readonly UITextPosition _beginningOfDocument = new AvaloniaTextPosition(0); private readonly UITextInputStringTokenizer _tokenizer; + private readonly NSString _textInputContextIdentifier = new NSString(Guid.NewGuid().ToString()); private bool _isInUpdate; public TextInputMethodClient? Client => _client; @@ -95,7 +96,7 @@ partial class AvaloniaView public override UIEditingInteractionConfiguration EditingInteractionConfiguration => UIEditingInteractionConfiguration.Default; - public override NSString TextInputContextIdentifier => new NSString(Guid.NewGuid().ToString()); + public override NSString TextInputContextIdentifier => _textInputContextIdentifier; public override UITextInputMode TextInputMode { From 0842236630eff07498bf306655eb6c35d8eba761 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 31 Mar 2026 13:48:55 +0200 Subject: [PATCH 11/86] Do not use visual children enumerator in TopLevelHost (#21048) * Add failing test for ExtendClientAreaToDecorationsHint in AttachedToVisualTree * Do not use visual children enumerator in TopLevelHost --- src/Avalonia.Controls/TopLevelHost.cs | 18 ++++++++---- .../WindowTests.cs | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/TopLevelHost.cs b/src/Avalonia.Controls/TopLevelHost.cs index 9592dd8221..afc4d59e7b 100644 --- a/src/Avalonia.Controls/TopLevelHost.cs +++ b/src/Avalonia.Controls/TopLevelHost.cs @@ -51,11 +51,14 @@ internal partial class TopLevelHost : Control var hasInset = inset != default; var desiredSize = default(Size); - foreach (var child in VisualChildren) + var children = VisualChildren; + var childrenCount = children.Count; + + for (var i = 0; i < childrenCount; i++) { - if (child is Layoutable l) + if (children[i] is Layoutable l) { - if (hasInset && ReferenceEquals(child, _topLevel)) + if (hasInset && ReferenceEquals(l, _topLevel)) { // In forced mode, measure the TopLevel with reduced size var contentSize = new Size( @@ -89,11 +92,14 @@ internal partial class TopLevelHost : Control var inset = _decorationInset; var hasInset = inset != default; - foreach (var child in VisualChildren) + var children = VisualChildren; + var childrenCount = children.Count; + + for (var i = 0; i < childrenCount; i++) { - if (child is Layoutable l) + if (children[i] is Layoutable l) { - if (hasInset && ReferenceEquals(child, _topLevel)) + if (hasInset && ReferenceEquals(l, _topLevel)) { // In forced mode, arrange the TopLevel within the inset area var contentSize = new Size( diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 63cc2db193..3b2386b1d5 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -710,6 +710,35 @@ namespace Avalonia.Controls.UnitTests Assert.False(visualRoot.HasMirrorTransform); } + [Fact] + public void Extending_Client_Area_To_Decorations_When_Attached_To_Visual_Tree_Works() + { + var extended = false; + + var windowImpl = MockWindowingPlatform.CreateWindowMock(); + windowImpl.Setup(w => w.NeedsManagedDecorations).Returns(() => extended); + windowImpl.Setup(w => w.RequestedDrawnDecorations).Returns(PlatformRequestedDrawnDecoration.TitleBar); + + using var app = UnitTestApplication.Start(TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object))); + + var border = new Border(); + + var window = new Window + { + Content = border + }; + + border.AttachedToVisualTree += + (_, _) => + { + extended = true; + windowImpl.Object.ExtendClientAreaToDecorationsChanged?.Invoke(true); + }; + + window.Show(); + } + public class SizingTests : ScopedTestBase { [Fact] From 412dca3aaeff60515e003b2842b88070f98719bb Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 31 Mar 2026 16:49:29 +0500 Subject: [PATCH 12/86] Expose allowed titlebar button actions from the platform implementation and respect them from drawn decorations (#21035) * Expose WM window action capabilities from X11. * Hide titlebar buttons for unsupported actions * Simplify subscription * Explose NetSupported property from X11Globals.cs, so it's easier to make checks from the rest of the codebase * shifts * comma --- .../Chrome/WindowDrawnDecorations.cs | 53 +++++++++++++++---- src/Avalonia.Controls/Platform/IWindowImpl.cs | 10 ++++ .../Platform/PlatformAllowedWindowActions.cs | 33 ++++++++++++ src/Avalonia.Controls/Window.cs | 16 ++++++ .../Controls/WindowDrawnDecorations.xaml | 14 +++++ .../Controls/WindowDrawnDecorations.xaml | 14 +++++ src/Avalonia.X11/X11Atoms.cs | 4 ++ src/Avalonia.X11/X11Globals.cs | 25 +++++++-- src/Avalonia.X11/X11Window.cs | 30 +++++++++++ .../X11WindowModes/DefaultWindowMode.cs | 3 +- 10 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 src/Avalonia.Controls/Platform/PlatformAllowedWindowActions.cs diff --git a/src/Avalonia.Controls/Chrome/WindowDrawnDecorations.cs b/src/Avalonia.Controls/Chrome/WindowDrawnDecorations.cs index 7a282bf306..26dffa8f5d 100644 --- a/src/Avalonia.Controls/Chrome/WindowDrawnDecorations.cs +++ b/src/Avalonia.Controls/Chrome/WindowDrawnDecorations.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Automation; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Layout; using Avalonia.LogicalTree; @@ -15,7 +16,8 @@ namespace Avalonia.Controls.Chrome; /// TopLevelHost extracts overlay/underlay/popover visuals from the template content /// and inserts them into its own visual tree. /// -[PseudoClasses(pcNormal, pcMaximized, pcFullscreen, pcHasShadow, pcHasBorder, pcHasTitlebar)] +[PseudoClasses(pcNormal, pcMaximized, pcFullscreen, pcHasShadow, pcHasBorder, pcHasTitlebar, + pcHasMaximize, pcHasFullscreen, pcHasMinimize)] [TemplatePart(PART_CloseButton, typeof(Button))] [TemplatePart(PART_MinimizeButton, typeof(Button))] [TemplatePart(PART_MaximizeButton, typeof(Button))] @@ -31,6 +33,9 @@ public class WindowDrawnDecorations : StyledElement internal const string pcHasShadow = ":has-shadow"; internal const string pcHasBorder = ":has-border"; internal const string pcHasTitlebar = ":has-titlebar"; + internal const string pcHasMaximize = ":has-maximize"; + internal const string pcHasFullscreen = ":has-fullscreen"; + internal const string pcHasMinimize = ":has-minimize"; // Template part names for caption buttons internal const string PART_CloseButton = "PART_CloseButton"; @@ -383,8 +388,11 @@ public class WindowDrawnDecorations : StyledElement Detach(); _hostWindow = window; + window.AllowedWindowActionsChanged += OnAllowedWindowActionsChanged; + _windowSubscriptions = new CompositeDisposable { + Disposable.Create(() => window.AllowedWindowActionsChanged -= OnAllowedWindowActionsChanged), window.GetObservable(Window.TitleProperty) .Subscribe(title => SetCurrentValue(TitleProperty, title)), window.GetObservable(Window.CanMaximizeProperty) @@ -407,6 +415,7 @@ public class WindowDrawnDecorations : StyledElement }), }; + UpdateAllowedActionsPseudoClasses(); UpdateMaximizeButtonState(); UpdateMinimizeButtonState(); UpdateFullScreenButtonState(); @@ -547,32 +556,54 @@ public class WindowDrawnDecorations : StyledElement e.Handled = true; } + private PlatformAllowedWindowActions EffectiveAllowedActions => + _hostWindow?.AllowedWindowActions ?? PlatformAllowedWindowActions.All; + private void UpdateMaximizeButtonState() { if (_maximizeButton == null) return; - _maximizeButton.IsEnabled = _hostWindow?.WindowState switch - { - WindowState.Maximized or WindowState.FullScreen => _hostWindow.CanResize, - WindowState.Normal => _hostWindow.CanMaximize, - _ => true - }; + _maximizeButton.IsEnabled = EffectiveAllowedActions.HasFlag(PlatformAllowedWindowActions.Maximize) + && (_hostWindow?.WindowState switch + { + WindowState.Maximized or WindowState.FullScreen => _hostWindow.CanResize, + WindowState.Normal => _hostWindow.CanMaximize, + _ => true + }); } private void UpdateMinimizeButtonState() { if (_minimizeButton == null) return; - _minimizeButton.IsEnabled = _hostWindow?.CanMinimize ?? true; + _minimizeButton.IsEnabled = EffectiveAllowedActions.HasFlag(PlatformAllowedWindowActions.Minimize) + && (_hostWindow?.CanMinimize ?? true); } private void UpdateFullScreenButtonState() { if (_fullScreenButton == null) return; - _fullScreenButton.IsEnabled = _hostWindow?.WindowState == WindowState.FullScreen - ? _hostWindow.CanResize - : _hostWindow?.CanMaximize ?? true; + _fullScreenButton.IsEnabled = EffectiveAllowedActions.HasFlag(PlatformAllowedWindowActions.Fullscreen) + && (_hostWindow?.WindowState == WindowState.FullScreen + ? _hostWindow.CanResize + : _hostWindow?.CanMaximize ?? true); + } + + private void OnAllowedWindowActionsChanged(PlatformAllowedWindowActions actions) + { + UpdateAllowedActionsPseudoClasses(); + UpdateMaximizeButtonState(); + UpdateMinimizeButtonState(); + UpdateFullScreenButtonState(); + } + + private void UpdateAllowedActionsPseudoClasses() + { + var actions = EffectiveAllowedActions; + PseudoClasses.Set(pcHasMaximize, actions.HasFlag(PlatformAllowedWindowActions.Maximize)); + PseudoClasses.Set(pcHasFullscreen, actions.HasFlag(PlatformAllowedWindowActions.Fullscreen)); + PseudoClasses.Set(pcHasMinimize, actions.HasFlag(PlatformAllowedWindowActions.Minimize)); } private void UpdateEffectiveGeometry() diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 42be775f0d..d864ca35c3 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -162,5 +162,15 @@ namespace Avalonia.Platform /// /// -1 for platform default, otherwise the height in DIPs. void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight); + + /// + /// Gets the window actions that the underlying platform currently allows. + /// + PlatformAllowedWindowActions AllowedWindowActions => PlatformAllowedWindowActions.All; + + /// + /// Gets or sets a callback invoked when changes. + /// + Action? AllowedWindowActionsChanged { get => null; set { } } } } diff --git a/src/Avalonia.Controls/Platform/PlatformAllowedWindowActions.cs b/src/Avalonia.Controls/Platform/PlatformAllowedWindowActions.cs new file mode 100644 index 0000000000..35871c0803 --- /dev/null +++ b/src/Avalonia.Controls/Platform/PlatformAllowedWindowActions.cs @@ -0,0 +1,33 @@ +using System; +using Avalonia.Metadata; + +namespace Avalonia.Controls.Platform; + +/// +/// Flags indicating which window actions the underlying platform supports. +/// +[Flags, PrivateApi] +public enum PlatformAllowedWindowActions +{ + None = 0, + + /// + /// The underlying platform supports maximizing/unmaximizing windows. + /// + Maximize = 1 << 0, + + /// + /// The underlying platform supports fullscreen mode. + /// + Fullscreen = 1 << 1, + + /// + /// The underlying platform supports minimizing windows. + /// + Minimize = 1 << 2, + + /// + /// All actions are supported (default when the underlying platform does not report capabilities). + /// + All = Maximize | Fullscreen | Minimize, +} diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 4dab3574eb..0797f39733 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -220,6 +220,7 @@ namespace Avalonia.Controls private bool _positionWasSet; private bool _wasShownBefore; private IDisposable? _modalSubscription; + private PlatformAllowedWindowActions _allowedWindowActions = PlatformAllowedWindowActions.All; /// /// Initializes static members of the class. @@ -250,6 +251,8 @@ namespace Avalonia.Controls impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; + impl.AllowedWindowActionsChanged = OnAllowedWindowActionsChanged; + _allowedWindowActions = impl.AllowedWindowActions; this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => { ResizePlatformImpl(x, WindowResizeReason.Application); @@ -485,6 +488,11 @@ namespace Avalonia.Controls set => SetValue(CanMaximizeProperty, value); } + /// + /// Gets the window actions currently allowed by the underlying platform. + /// + internal PlatformAllowedWindowActions AllowedWindowActions => _allowedWindowActions; + /// /// Gets or sets the icon of the window. /// @@ -673,6 +681,14 @@ namespace Avalonia.Controls UpdateDrawnDecorationParts(); } + internal event Action? AllowedWindowActionsChanged; + + private void OnAllowedWindowActionsChanged(PlatformAllowedWindowActions actions) + { + _allowedWindowActions = actions; + AllowedWindowActionsChanged?.Invoke(actions); + } + private void ExtendClientAreaToDecorationsChanged(bool isExtended) { IsExtendedIntoWindowDecorations = isExtended; diff --git a/src/Avalonia.Themes.Fluent/Controls/WindowDrawnDecorations.xaml b/src/Avalonia.Themes.Fluent/Controls/WindowDrawnDecorations.xaml index b2d66fdbbd..3ccda42ce3 100644 --- a/src/Avalonia.Themes.Fluent/Controls/WindowDrawnDecorations.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/WindowDrawnDecorations.xaml @@ -196,6 +196,20 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Simple/Controls/TextSelectionHandle.xaml b/src/Avalonia.Themes.Simple/Controls/TextSelectionHandle.xaml index c9ca5ebc63..4d8c4b0d90 100644 --- a/src/Avalonia.Themes.Simple/Controls/TextSelectionHandle.xaml +++ b/src/Avalonia.Themes.Simple/Controls/TextSelectionHandle.xaml @@ -1,6 +1,63 @@  - 32 + 24 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + - + + + + diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index fa578a7afe..9d7b8a6118 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -1537,11 +1537,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(2,4)] - [InlineData(0,4)] - [InlineData(2,6)] - [InlineData(0,6)] - [InlineData(3,4)] + [InlineData(2, 4)] + [InlineData(0, 4)] + [InlineData(2, 6)] + [InlineData(0, 6)] + [InlineData(3, 4)] public void When_Selection_From_Left_To_Right_Pressing_Right_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1566,11 +1566,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(2,4)] - [InlineData(0,4)] - [InlineData(2,6)] - [InlineData(0,6)] - [InlineData(3,4)] + [InlineData(2, 4)] + [InlineData(0, 4)] + [InlineData(2, 6)] + [InlineData(0, 6)] + [InlineData(3, 4)] public void When_Selection_From_Left_To_Right_Pressing_Left_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1595,11 +1595,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(4,2)] - [InlineData(4,0)] - [InlineData(6,2)] - [InlineData(6,0)] - [InlineData(4,3)] + [InlineData(4, 2)] + [InlineData(4, 0)] + [InlineData(6, 2)] + [InlineData(6, 0)] + [InlineData(4, 3)] public void When_Selection_From_Right_To_Left_Pressing_Right_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1624,11 +1624,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(4,2)] - [InlineData(4,0)] - [InlineData(6,2)] - [InlineData(6,0)] - [InlineData(4,3)] + [InlineData(4, 2)] + [InlineData(4, 0)] + [InlineData(6, 2)] + [InlineData(6, 0)] + [InlineData(4, 3)] public void When_Selection_From_Right_To_Left_Pressing_Left_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1707,11 +1707,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(2,4)] - [InlineData(0,4)] - [InlineData(2,6)] - [InlineData(0,6)] - [InlineData(3,4)] + [InlineData(2, 4)] + [InlineData(0, 4)] + [InlineData(2, 6)] + [InlineData(0, 6)] + [InlineData(3, 4)] public void When_Selection_From_Left_To_Right_Pressing_Up_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1736,11 +1736,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(4,2)] - [InlineData(4,0)] - [InlineData(6,2)] - [InlineData(6,0)] - [InlineData(4,3)] + [InlineData(4, 2)] + [InlineData(4, 0)] + [InlineData(6, 2)] + [InlineData(6, 0)] + [InlineData(4, 3)] public void When_Selection_From_Right_To_Left_Pressing_Up_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1792,11 +1792,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(2,4)] - [InlineData(0,4)] - [InlineData(2,6)] - [InlineData(0,6)] - [InlineData(3,4)] + [InlineData(2, 4)] + [InlineData(0, 4)] + [InlineData(2, 6)] + [InlineData(0, 6)] + [InlineData(3, 4)] public void When_Selection_From_Left_To_Right_Pressing_Down_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1821,11 +1821,11 @@ namespace Avalonia.Controls.UnitTests } [Theory] - [InlineData(4,2)] - [InlineData(4,0)] - [InlineData(6,2)] - [InlineData(6,0)] - [InlineData(4,3)] + [InlineData(4, 2)] + [InlineData(4, 0)] + [InlineData(6, 2)] + [InlineData(6, 0)] + [InlineData(4, 3)] public void When_Selection_From_Right_To_Left_Pressing_Down_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_Input.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_Input.cs new file mode 100644 index 0000000000..e19bc5ca9a --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_Input.cs @@ -0,0 +1,278 @@ +#nullable enable + +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Harfbuzz; +using Avalonia.Headless; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.UnitTests; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class TextBoxTests_Input : ScopedTestBase + { + [Fact] + public void Touch_Tap_Moves_Caret() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "12 12345678" + }; + + var root = new TestRoot() + { + Child = target + }; + + target.ApplyTemplate(); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + var touch = new TouchTestHelper(); + + Assert.Equal(target.CaretIndex, 0); + + // Move to index 8 + touch.Down(target, new Point(50, 0)); + touch.Up(target, new Point(50, 0)); + + Assert.Equal(target.CaretIndex, 8); + } + } + + [Fact] + public void Touch_Double_Tap_Selects_Word() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "12 12345678" + }; + + var root = new TestRoot() + { + Child = target + }; + + target.ApplyTemplate(); + + + root.LayoutManager.ExecuteInitialLayoutPass(); + + var touch = new TouchTestHelper(); + + Assert.Equal(target.CaretIndex, 0); + + // Move to index 8 + touch.Down(target, new Point(50, 0)); + touch.Up(target, new Point(50, 0)); + + // Double tap + touch.Down(target, new Point(50, 0)); + touch.Up(target, new Point(50, 0)); + + Assert.Equal(target.SelectionStart, 3); + Assert.Equal(target.SelectionEnd, 11); + Assert.Equal(target.CaretIndex, 8); + } + } + + [Fact] + public void Touch_Hold_Selects_Word() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "12 12345678" + }; + + var root = new TestRoot() + { + Child = target + }; + + target.ApplyTemplate(); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + var touch = new TouchTestHelper(); + + Assert.Equal(target.CaretIndex, 0); + + // Move to index 8 + touch.Down(target, new Point(50, 0)); + + var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests()); + timer.ForceFire(); + touch.Up(target, new Point(50, 0)); + + Assert.Equal(target.SelectionStart, 3); + Assert.Equal(target.SelectionEnd, 11); + Assert.Equal(target.CaretIndex, 8); + } + } + + [Fact] + public void Touch_Hold_On_Selection_Requests_Context() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "12 12345678" + }; + + var root = new TestRoot() + { + Child = target + }; + + target.ApplyTemplate(); + bool requested = false; + + target.SelectionStart = 3; + target.SelectionEnd = 11; + + target.ContextRequested += Target_ContextRequested; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + var touch = new TouchTestHelper(); + + Assert.Equal(target.CaretIndex, 0); + + // Move to index 8 + touch.Down(target, new Point(50, 0)); + + var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests()); + timer.ForceFire(); + touch.Up(target, new Point(50, 0)); + + Assert.True(requested); + + void Target_ContextRequested(object? sender, ContextRequestedEventArgs e) + { + requested = true; + } + } + } + + private static TestServices Services => TestServices.MockThreadingInterface.With( + standardCursorFactory: Mock.Of(), + renderInterface: new HeadlessPlatformRenderInterface(), + textShaperImpl: new HarfBuzzTextShaper(), + fontManagerImpl: new TestFontManager(), + assetLoader: new StandardAssetLoader()); + + internal static IControlTemplate CreateTemplate() + { + return new FuncControlTemplate((control, scope) => + new ScrollViewer + { + Name = "PART_ScrollViewer", + Template = new FuncControlTemplate(ScrollViewerTests.CreateTemplate), + Content = new TextPresenter + { + Name = "PART_TextPresenter", + [!!TextPresenter.TextProperty] = new Binding + { + Path = nameof(TextPresenter.Text), + Mode = BindingMode.TwoWay, + Priority = BindingPriority.Template, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + }, + [!!TextPresenter.CaretIndexProperty] = new Binding + { + Path = nameof(TextPresenter.CaretIndex), + Mode = BindingMode.TwoWay, + Priority = BindingPriority.Template, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + } + }.RegisterInNameScope(scope) + }.RegisterInNameScope(scope)); + } + + private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers) + { + textBox.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + KeyModifiers = inputModifiers, + Key = key + }); + } + + private static void RaiseTextEvent(TextBox textBox, string text) + { + textBox.RaiseEvent(new TextInputEventArgs + { + RoutedEvent = InputElement.TextInputEvent, + Text = text + }); + } + + private class Class1 : NotifyingBase + { + private int _foo; + private string? _bar; + + public int Foo + { + get { return _foo; } + set { _foo = value; RaisePropertyChanged(); } + } + + public string? Bar + { + get { return _bar; } + set { _bar = value; RaisePropertyChanged(); } + } + } + + private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl) + { + } + + private static Mock CreateMockTopLevelImpl() + { + var clipboard = new Mock(); + clipboard.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); + clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard))) + .Returns(new Clipboard(new HeadlessClipboardImplStub())); + clipboard.SetupGet(x => x.RenderScaling).Returns(1); + return clipboard; + } + + private static FuncControlTemplate CreateTopLevelTemplate() + { + return new FuncControlTemplate((x, scope) => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], + }.RegisterInNameScope(scope)); + } + + private class TestContextMenu : ContextMenu + { + public TestContextMenu() + { + IsOpen = true; + } + } + } +} diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs index 3ec49fa321..7a13425997 100644 --- a/tests/Avalonia.UnitTests/TouchTestHelper.cs +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -7,6 +7,7 @@ namespace Avalonia.UnitTests { private readonly Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Touch, true); private ulong _nextStamp = 1; + private int _clickCount = 0; private ulong Timestamp() => _nextStamp++; public IInputElement? Captured => _pointer.Captured; @@ -20,7 +21,7 @@ namespace Avalonia.UnitTests _pointer.Capture((IInputElement)target); source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (Visual)source, position, Timestamp(), new(RawInputModifiers.LeftMouseButton, PointerUpdateKind.LeftButtonPressed), - modifiers)); + modifiers, ++_clickCount)); } public void Move(Interactive target, in Point position, KeyModifiers modifiers = default) => Move(target, target, position, modifiers); From 5d5fce8d9b1330a07cfd2ad0739180c403a91eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Tue, 31 Mar 2026 16:54:40 +0200 Subject: [PATCH 17/86] Add keyboard navigation for overflow CommandBar popup (#21005) * Fix keyboard navigation in CommandBar overflow * More fixes * More changes * More changes, added more tests --- .../CommandBar/CommandBarKeyboardPage.xaml | 121 ++++++++++ .../CommandBar/CommandBarKeyboardPage.xaml.cs | 93 ++++++++ .../Pages/CommandBarPage.xaml.cs | 1 + .../CommandBar/CommandBar.cs | 128 +++++++++- .../Controls/CommandBar.xaml | 5 +- .../Controls/CommandBar.xaml | 5 +- .../CommandBarTests.cs | 223 ++++++++++++++++++ 7 files changed, 569 insertions(+), 7 deletions(-) create mode 100644 samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml create mode 100644 samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml.cs diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml new file mode 100644 index 0000000000..7b820b0183 --- /dev/null +++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml @@ -0,0 +1,121 @@ + + + M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z + M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z + M15.6,10.79C17.04,10.07 18,8.64 18,7C18,4.79 16.21,3 14,3H7V21H14.73C16.78,21 18.5,19.37 18.5,17.32C18.5,15.82 17.72,14.53 16.5,13.77C16.2,13.59 15.9,13.44 15.6,13.32V10.79M10,6.5H13C13.83,6.5 14.5,7.17 14.5,8C14.5,8.83 13.83,9.5 13,9.5H10V6.5M13.5,17.5H10V14H13.5C14.33,14 15,14.67 15,15.5C15,16.33 14.33,17.5 13.5,17.5Z + M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9,11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8A3,3 0 0,0 21,5A3,3 0 0,0 18,2A3,3 0 0,0 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79,9 6,9A3,3 0 0,0 3,12A3,3 0 0,0 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11,18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92,20.61 20.92,19C20.92,17.39 19.61,16.08 18,16.08Z + M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z + + + + + + + + + + + + + + + + - [TemplatePart("PART_OverflowButton", typeof(Button))] + [TemplatePart("PART_OverflowButton", typeof(Button))] [TemplatePart("PART_OverflowPopup", typeof(Popup))] - [TemplatePart("PART_ContentPresenter", typeof(Control))] + [TemplatePart("PART_OverflowPresenter", typeof(ItemsControl))] + [TemplatePart("PART_ContentPresenter", typeof(Control))] public class CommandBar : TemplatedControl { /// @@ -129,12 +131,14 @@ namespace Avalonia.Controls private Button? _overflowButton; private Popup? _overflowPopup; + private ItemsControl? _overflowPresenter; private Control? _contentPresenter; private readonly ObservableCollection _visiblePrimaryCommands = new(); private readonly ObservableCollection _overflowItems = new(); private bool _isDynamicUpdateInProgress; private double _constraintWidth = double.PositiveInfinity; + private bool _openedViaKeyboard; public CommandBar() { @@ -348,14 +352,33 @@ namespace Avalonia.Controls base.OnApplyTemplate(e); if (_overflowButton != null) + { _overflowButton.Click -= OnOverflowButtonClick; + _overflowButton.GotFocus -= OnOverflowButtonGotFocus; + _overflowButton.RemoveHandler(Input.InputElement.KeyDownEvent, OnOverflowButtonKeyDown); + _overflowButton.RemoveHandler(Input.InputElement.PointerPressedEvent, OnOverflowButtonPointerPressed); + } + if (_overflowPresenter != null) + _overflowPresenter.KeyDown -= OnOverflowPresenterKeyDown; + if (_overflowPopup != null) + _overflowPopup.Opened -= OnOverflowPopupOpened; _overflowButton = e.NameScope.Find - - + + + + + - - + diff --git a/samples/ControlCatalog/Pages/AcceleratorPage.xaml.cs b/samples/ControlCatalog/Pages/AcceleratorPage.xaml.cs index e013d9aaac..40afacc053 100644 --- a/samples/ControlCatalog/Pages/AcceleratorPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AcceleratorPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class AcceleratorPage : UserControl + public partial class AcceleratorPage : ContentPage { public AcceleratorPage() { diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 4fd0933879..4a68fbfafd 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -1,12 +1,13 @@ - - + - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + - + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs b/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs index 9516655bb9..adb5b05706 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs @@ -2,7 +2,7 @@ namespace ControlCatalog.Pages { - public partial class AcrylicPage : UserControl + public partial class AcrylicPage : ContentPage { public AcrylicPage() { diff --git a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml index e9a245a8e1..35907c7ef8 100644 --- a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml +++ b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml @@ -1,52 +1,52 @@ - + + + Rotation + + - - - Rotation - - + + - + + + + + + + + + + + + + + - - - - + + + + diff --git a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml.cs b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml.cs index 526b8a492b..5ee8d44479 100644 --- a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class AdornerLayerPage : UserControl + public partial class AdornerLayerPage : ContentPage { private Control? _adorner; diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index 35e917f996..4edb00b7c3 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -1,79 +1,83 @@ - - - A control into which the user can input text + + + A control into which the user can input text - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 47b6eb7f4a..295aa9d139 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -11,7 +11,7 @@ using ControlCatalog.Models; namespace ControlCatalog.Pages { - public partial class AutoCompleteBoxPage : UserControl + public partial class AutoCompleteBoxPage : ContentPage { private static StateData[] BuildAllStates() { diff --git a/samples/ControlCatalog/Pages/BitmapCachePage.axaml b/samples/ControlCatalog/Pages/BitmapCachePage.axaml index 5902be8e51..4298f29e8f 100644 --- a/samples/ControlCatalog/Pages/BitmapCachePage.axaml +++ b/samples/ControlCatalog/Pages/BitmapCachePage.axaml @@ -1,45 +1,49 @@ - + - - - - - - - Render at scale - - Scale - - Enable clear type + + + + + + + Render at scale + + Scale + + Enable clear type - Snap to device pixels - Subpixel offset X - - - - - - - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - - - + Snap to device pixels + Subpixel offset X + + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + - + + + - + + diff --git a/samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs b/samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs index 92b3cbec68..be7cf7a7ea 100644 --- a/samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs +++ b/samples/ControlCatalog/Pages/BitmapCachePage.axaml.cs @@ -4,10 +4,10 @@ using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages; -public partial class BitmapCachePage : UserControl +public partial class BitmapCachePage : ContentPage { public BitmapCachePage() { InitializeComponent(); } -} \ No newline at end of file +} diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml index c811ddaa22..297e3fcfb8 100644 --- a/samples/ControlCatalog/Pages/BorderPage.xaml +++ b/samples/ControlCatalog/Pages/BorderPage.xaml @@ -1,61 +1,65 @@ - - - - - - A control which decorates a child with a border and background + + + + + + A control which decorates a child with a border and background - - - Border - - - Background And CenterBorder - - - Background And InnerBorderEdge - - - Background And OuterBorderEdge - - - Rounded Corners - - - Rounded Corners - - - - - + + + Border + + + Background And CenterBorder + + + Background And InnerBorderEdge + + + Background And OuterBorderEdge + + + Rounded Corners + + + Rounded Corners + + + + + + - - + + diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml.cs b/samples/ControlCatalog/Pages/BorderPage.xaml.cs index 944df86f8c..71aebf24ff 100644 --- a/samples/ControlCatalog/Pages/BorderPage.xaml.cs +++ b/samples/ControlCatalog/Pages/BorderPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class BorderPage : UserControl + public partial class BorderPage : ContentPage { public BorderPage() { diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml index 900e304559..c21a772c2a 100644 --- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml @@ -1,6 +1,7 @@ - @@ -30,4 +31,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs index fad4b466fe..28f4b20084 100644 --- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs @@ -3,7 +3,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class ButtonSpinnerPage : UserControl + public partial class ButtonSpinnerPage : ContentPage { public ButtonSpinnerPage() { diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml b/samples/ControlCatalog/Pages/ButtonsPage.xaml index f13581aa18..695ae28515 100644 --- a/samples/ControlCatalog/Pages/ButtonsPage.xaml +++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml @@ -1,8 +1,9 @@ - - + @@ -16,9 +17,9 @@ - + - + - + @@ -288,4 +289,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs index 6dcee1ab7d..611fc76241 100644 --- a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs @@ -3,7 +3,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class ButtonsPage : UserControl + public partial class ButtonsPage : ContentPage { private int repeatButtonClickCount = 0; diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml index 8734758d26..769dbc0dac 100644 --- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml @@ -1,7 +1,8 @@ - A control for selecting dates with a calendar drop-down @@ -52,4 +53,4 @@ - + diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs index 73e1fe4bba..0e4fcbd986 100644 --- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs @@ -3,7 +3,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class CalendarDatePickerPage : UserControl + public partial class CalendarDatePickerPage : ContentPage { public CalendarDatePickerPage() { diff --git a/samples/ControlCatalog/Pages/CalendarPage.xaml b/samples/ControlCatalog/Pages/CalendarPage.xaml index e142c4da72..99727e9124 100644 --- a/samples/ControlCatalog/Pages/CalendarPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarPage.xaml @@ -1,5 +1,6 @@ - A calendar control for selecting dates @@ -49,4 +50,4 @@ - + diff --git a/samples/ControlCatalog/Pages/CalendarPage.xaml.cs b/samples/ControlCatalog/Pages/CalendarPage.xaml.cs index 56f511be55..d9118326f9 100644 --- a/samples/ControlCatalog/Pages/CalendarPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CalendarPage.xaml.cs @@ -3,7 +3,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class CalendarPage : UserControl + public partial class CalendarPage : ContentPage { public CalendarPage() { diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml b/samples/ControlCatalog/Pages/CanvasPage.xaml index 416164f391..a9e342181d 100644 --- a/samples/ControlCatalog/Pages/CanvasPage.xaml +++ b/samples/ControlCatalog/Pages/CanvasPage.xaml @@ -1,5 +1,6 @@ - A panel which lays out its children by explicit coordinates @@ -34,4 +35,4 @@ - + diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml.cs b/samples/ControlCatalog/Pages/CanvasPage.xaml.cs index e52df597a9..b94aabca94 100644 --- a/samples/ControlCatalog/Pages/CanvasPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CanvasPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class CanvasPage : UserControl + public partial class CanvasPage : ContentPage { public CanvasPage() { diff --git a/samples/ControlCatalog/Pages/CarouselDemoPage.xaml b/samples/ControlCatalog/Pages/CarouselDemoPage.xaml index df4317fcad..792441ddb4 100644 --- a/samples/ControlCatalog/Pages/CarouselDemoPage.xaml +++ b/samples/ControlCatalog/Pages/CarouselDemoPage.xaml @@ -1,11 +1,14 @@ - - - - - - - + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs index 36c9961658..0d59d296f6 100644 --- a/samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CarouselDemoPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class CarouselDemoPage : UserControl + public partial class CarouselDemoPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml b/samples/ControlCatalog/Pages/CarouselPage.xaml index c6e20fec5b..bdb40b9038 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml @@ -1,5 +1,6 @@ - A swipeable items control that can reveal adjacent pages with ViewportFraction. @@ -115,4 +116,4 @@ - + diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs index 0a0c973b90..c4a6ffa12f 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs @@ -7,7 +7,7 @@ using ControlCatalog.Pages.Transitions; namespace ControlCatalog.Pages { - public partial class CarouselPage : UserControl + public partial class CarouselPage : ContentPage { public CarouselPage() { diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml b/samples/ControlCatalog/Pages/CheckBoxPage.xaml index 2f60fc5dae..4f9c59448b 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml @@ -1,5 +1,6 @@ - A check box control @@ -25,4 +26,4 @@ - + diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs index 95ff21ef6d..b63d7084d6 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class CheckBoxPage : UserControl + public partial class CheckBoxPage : ContentPage { public CheckBoxPage() { diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml b/samples/ControlCatalog/Pages/ClipboardPage.xaml index 864a520aca..86e99b9305 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml @@ -1,5 +1,6 @@ - Example of ClipboardPage capabilities @@ -27,4 +28,4 @@ /> - + diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs index 65f58d0444..0bc5447313 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs @@ -15,7 +15,7 @@ using Avalonia.Threading; namespace ControlCatalog.Pages { - public partial class ClipboardPage : UserControl + public partial class ClipboardPage : ContentPage { private readonly DataFormat _customBinaryDataFormat = DataFormat.CreateBytesApplicationFormat("controlcatalog-binary-data"); diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index fb397085e7..f0b303e0d4 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -1,4 +1,4 @@ - - + - + - + diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs index 4d2d236ed3..cc225b576d 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs @@ -5,7 +5,7 @@ using Avalonia.Media; namespace ControlCatalog.Pages { - public partial class ColorPickerPage : UserControl + public partial class ColorPickerPage : ContentPage { public ColorPickerPage() { diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml index 449c10f000..149fd73d0a 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml @@ -1,4 +1,4 @@ - - + diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs index 301b052cb8..720aae9ef5 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs @@ -4,7 +4,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class ComboBoxPage : UserControl + public partial class ComboBoxPage : ContentPage { public ComboBoxPage() { diff --git a/samples/ControlCatalog/Pages/CommandBarPage.xaml b/samples/ControlCatalog/Pages/CommandBarPage.xaml index 6cf280ea95..3c648ea012 100644 --- a/samples/ControlCatalog/Pages/CommandBarPage.xaml +++ b/samples/ControlCatalog/Pages/CommandBarPage.xaml @@ -1,8 +1,11 @@ - - + - - - + + + + + diff --git a/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs index b759c7b605..0b81c698d2 100644 --- a/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CommandBarPage.xaml.cs @@ -7,7 +7,7 @@ using Avalonia.Media; namespace ControlCatalog.Pages { - public partial class CommandBarPage : UserControl + public partial class CommandBarPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml b/samples/ControlCatalog/Pages/CompositionPage.axaml index 4d9bb41781..a3e9b047a8 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml @@ -1,62 +1,61 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - Resize me - - - + + + + + + + + + + + + + + + + + + + + + + + + + Resize me + + + + + + + + + + + + + + + + + + Precise dirty rects - - - - - - - - - - - - - - Precise dirty rects - - - - - - + + + + diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index 287bde0be8..8f5d75f4ce 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -15,7 +15,7 @@ using Math = System.Math; namespace ControlCatalog.Pages; -public partial class CompositionPage : UserControl +public partial class CompositionPage : TabbedPage { private ImplicitAnimationCollection? _implicitAnimations; private CompositionCustomVisual? _customVisual; diff --git a/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml index bf37b4cc4f..ca4a85af8d 100644 --- a/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml +++ b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml @@ -1,6 +1,8 @@ - + - + + diff --git a/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml.cs b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml.cs index 0c13bb360e..5d666730ae 100644 --- a/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ConnectedAnimationDemoPage.xaml.cs @@ -7,7 +7,7 @@ using Avalonia.Media; namespace ControlCatalog.Pages { - public partial class ConnectedAnimationDemoPage : UserControl + public partial class ConnectedAnimationDemoPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = []; diff --git a/samples/ControlCatalog/Pages/ContainerQueryPage.xaml b/samples/ControlCatalog/Pages/ContainerQueryPage.xaml index e6b0558a04..96f7a87586 100644 --- a/samples/ControlCatalog/Pages/ContainerQueryPage.xaml +++ b/samples/ControlCatalog/Pages/ContainerQueryPage.xaml @@ -1,9 +1,10 @@ - @@ -107,4 +108,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ContainerQueryPage.xaml.cs b/samples/ControlCatalog/Pages/ContainerQueryPage.xaml.cs index acd67c6629..b43723005e 100644 --- a/samples/ControlCatalog/Pages/ContainerQueryPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContainerQueryPage.xaml.cs @@ -2,7 +2,7 @@ namespace ControlCatalog.Pages { - public partial class ContainerQueryPage : UserControl + public partial class ContainerQueryPage : ContentPage { public ContainerQueryPage() { diff --git a/samples/ControlCatalog/Pages/ContentDemoPage.xaml b/samples/ControlCatalog/Pages/ContentDemoPage.xaml index d8f2155a1b..bcf0ca67ef 100644 --- a/samples/ControlCatalog/Pages/ContentDemoPage.xaml +++ b/samples/ControlCatalog/Pages/ContentDemoPage.xaml @@ -1,11 +1,14 @@ - - - - - - - + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ContentDemoPage.xaml.cs b/samples/ControlCatalog/Pages/ContentDemoPage.xaml.cs index e667cfc0ff..b47dde07c1 100644 --- a/samples/ControlCatalog/Pages/ContentDemoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContentDemoPage.xaml.cs @@ -7,7 +7,7 @@ using Avalonia.Media; namespace ControlCatalog.Pages { - public partial class ContentDemoPage : UserControl + public partial class ContentDemoPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml index c23393e63c..fa52c05bfa 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml @@ -1,18 +1,19 @@ - - + - + @@ -156,4 +157,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs index bef2c8212d..f25b9341c3 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs @@ -7,7 +7,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class ContextFlyoutPage : UserControl + public partial class ContextFlyoutPage : ContentPage { public ContextFlyoutPage() { diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml b/samples/ControlCatalog/Pages/ContextMenuPage.xaml index 0e7c7b9451..4d1d02aed8 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml @@ -1,7 +1,8 @@ - A right click menu that can be applied to any control. @@ -86,4 +87,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index a71398181c..86ce807127 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -6,7 +6,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class ContextMenuPage : UserControl + public partial class ContextMenuPage : ContentPage { public ContextMenuPage() { diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml b/samples/ControlCatalog/Pages/CursorPage.xaml index 66f2b8b2e3..ae20193c97 100644 --- a/samples/ControlCatalog/Pages/CursorPage.xaml +++ b/samples/ControlCatalog/Pages/CursorPage.xaml @@ -1,6 +1,7 @@ - @@ -27,4 +28,4 @@ - + diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml.cs b/samples/ControlCatalog/Pages/CursorPage.xaml.cs index f9119175e2..88699ac6f2 100644 --- a/samples/ControlCatalog/Pages/CursorPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CursorPage.xaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class CursorPage : UserControl + public partial class CursorPage : ContentPage { public CursorPage() { diff --git a/samples/ControlCatalog/Pages/CustomDrawing.xaml b/samples/ControlCatalog/Pages/CustomDrawing.xaml index 04b7fcfea5..f1cb2dab14 100644 --- a/samples/ControlCatalog/Pages/CustomDrawing.xaml +++ b/samples/ControlCatalog/Pages/CustomDrawing.xaml @@ -1,15 +1,16 @@ - - + - + @@ -104,4 +105,4 @@ - + diff --git a/samples/ControlCatalog/Pages/CustomDrawing.xaml.cs b/samples/ControlCatalog/Pages/CustomDrawing.xaml.cs index d3fedb76be..0eae942075 100644 --- a/samples/ControlCatalog/Pages/CustomDrawing.xaml.cs +++ b/samples/ControlCatalog/Pages/CustomDrawing.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class CustomDrawing : UserControl + public partial class CustomDrawing : ContentPage { public CustomDrawing() { diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 3187e13f9f..04adf56700 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -1,5 +1,6 @@ - @@ -8,4 +9,4 @@ Click="OnLinkClicked" /> - + diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs index 5fa4ec8b0e..1154506e2e 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages; -public partial class DataGridPage : UserControl +public partial class DataGridPage : ContentPage { public DataGridPage() { diff --git a/samples/ControlCatalog/Pages/DataValidationPage.axaml b/samples/ControlCatalog/Pages/DataValidationPage.axaml index d46562addd..de4e398725 100644 --- a/samples/ControlCatalog/Pages/DataValidationPage.axaml +++ b/samples/ControlCatalog/Pages/DataValidationPage.axaml @@ -1,14 +1,15 @@ - - + - + - + diff --git a/samples/ControlCatalog/Pages/DataValidationPage.axaml.cs b/samples/ControlCatalog/Pages/DataValidationPage.axaml.cs index e38f85fec4..26bb75ca79 100644 --- a/samples/ControlCatalog/Pages/DataValidationPage.axaml.cs +++ b/samples/ControlCatalog/Pages/DataValidationPage.axaml.cs @@ -4,7 +4,7 @@ using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages; -public partial class DataValidationPage : UserControl +public partial class DataValidationPage : ContentPage { public DataValidationPage() { diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml index 7a1bfaa824..2183204c05 100644 --- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml +++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml @@ -1,7 +1,8 @@ - @@ -207,4 +208,4 @@ - + diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs index b5968a6b2a..99250d5fb0 100644 --- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs @@ -2,7 +2,7 @@ namespace ControlCatalog.Pages { - public partial class DateTimePickerPage : UserControl + public partial class DateTimePickerPage : ContentPage { public DateTimePickerPage() { diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index e6b3568909..296b632b91 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -1,6 +1,7 @@ - - + diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index adcf844552..616e3d4f62 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -12,7 +12,7 @@ using Avalonia.Platform.Storage; namespace ControlCatalog.Pages { - public partial class DialogsPage : UserControl + public partial class DialogsPage : ContentPage { public DialogsPage() { diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml index 7982ddc1d0..2ca9ed27a0 100644 --- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml @@ -1,15 +1,16 @@ - - + - + Example of Drag+Drop capabilities @@ -57,4 +58,4 @@ - + diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs index fe2b306477..b2a91da381 100644 --- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs @@ -13,7 +13,7 @@ using Avalonia.Platform.Storage; namespace ControlCatalog.Pages { - public partial class DragAndDropPage : UserControl + public partial class DragAndDropPage : ContentPage { private readonly DataFormat _customFormat = DataFormat.CreateStringApplicationFormat("xxx-avalonia-controlcatalog-custom"); diff --git a/samples/ControlCatalog/Pages/DrawerDemoPage.xaml b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml index 671a9a8487..f1edd0bc63 100644 --- a/samples/ControlCatalog/Pages/DrawerDemoPage.xaml +++ b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml @@ -1,11 +1,14 @@ - - - - - - - + + + + + + + + diff --git a/samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs index cd8aa9cd5b..27eb93ff03 100644 --- a/samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DrawerDemoPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class DrawerDemoPage : UserControl + public partial class DrawerDemoPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml index b5a2e6cdd0..322da30a61 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml @@ -1,7 +1,8 @@ - Expands to show nested content @@ -72,4 +73,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs b/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs index 05c5cbee53..65b7b7e30b 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class ExpanderPage : UserControl + public partial class ExpanderPage : ContentPage { public ExpanderPage() { diff --git a/samples/ControlCatalog/Pages/FlyoutsPage.axaml b/samples/ControlCatalog/Pages/FlyoutsPage.axaml index 64a005c987..840557bb9f 100644 --- a/samples/ControlCatalog/Pages/FlyoutsPage.axaml +++ b/samples/ControlCatalog/Pages/FlyoutsPage.axaml @@ -1,11 +1,12 @@ - - + @@ -25,7 +26,7 @@ - + @@ -282,4 +283,4 @@ - + diff --git a/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs b/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs index 91e92bc227..d6f54df074 100644 --- a/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs +++ b/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs @@ -7,7 +7,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class FlyoutsPage : UserControl + public partial class FlyoutsPage : ContentPage { public FlyoutsPage() { diff --git a/samples/ControlCatalog/Pages/FocusPage.xaml b/samples/ControlCatalog/Pages/FocusPage.xaml index f4bad7d138..6cb2647907 100644 --- a/samples/ControlCatalog/Pages/FocusPage.xaml +++ b/samples/ControlCatalog/Pages/FocusPage.xaml @@ -1,9 +1,10 @@ - @@ -47,4 +48,4 @@ - + diff --git a/samples/ControlCatalog/Pages/FocusPage.xaml.cs b/samples/ControlCatalog/Pages/FocusPage.xaml.cs index 2cc8067885..18aae471ab 100644 --- a/samples/ControlCatalog/Pages/FocusPage.xaml.cs +++ b/samples/ControlCatalog/Pages/FocusPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages; -public partial class FocusPage : UserControl +public partial class FocusPage : ContentPage { public FocusPage() { diff --git a/samples/ControlCatalog/Pages/GesturePage.xaml b/samples/ControlCatalog/Pages/GesturePage.xaml index bce18eab1d..da9ed5f807 100644 --- a/samples/ControlCatalog/Pages/GesturePage.xaml +++ b/samples/ControlCatalog/Pages/GesturePage.xaml @@ -1,11 +1,14 @@ - - - - - - - + + + + + + + + diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.xaml.cs similarity index 96% rename from samples/ControlCatalog/Pages/GesturePage.cs rename to samples/ControlCatalog/Pages/GesturePage.xaml.cs index d8b89e0ecb..56c09573d0 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class GesturePage : UserControl + public partial class GesturePage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/HeaderedContentPage.axaml b/samples/ControlCatalog/Pages/HeaderedContentPage.axaml index 86a2b00b72..cbf12695fd 100644 --- a/samples/ControlCatalog/Pages/HeaderedContentPage.axaml +++ b/samples/ControlCatalog/Pages/HeaderedContentPage.axaml @@ -1,8 +1,9 @@ - - + diff --git a/samples/ControlCatalog/Pages/HeaderedContentPage.axaml.cs b/samples/ControlCatalog/Pages/HeaderedContentPage.axaml.cs index ab967c4214..bfe033f90c 100644 --- a/samples/ControlCatalog/Pages/HeaderedContentPage.axaml.cs +++ b/samples/ControlCatalog/Pages/HeaderedContentPage.axaml.cs @@ -4,7 +4,7 @@ using Avalonia.Markup.Xaml; namespace ControlCatalog.Pages; -public partial class HeaderedContentPage : UserControl +public partial class HeaderedContentPage : ContentPage { public HeaderedContentPage() { diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml index 444d370e42..abd9ff47b0 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml +++ b/samples/ControlCatalog/Pages/ImagePage.xaml @@ -1,5 +1,6 @@ - @@ -65,4 +66,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs index f8630d7db6..eccdb9435f 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs +++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs @@ -5,7 +5,7 @@ using Avalonia.Media.Imaging; namespace ControlCatalog.Pages { - public partial class ImagePage : UserControl + public partial class ImagePage : ContentPage { public ImagePage() { diff --git a/samples/ControlCatalog/Pages/LabelsPage.axaml b/samples/ControlCatalog/Pages/LabelsPage.axaml index 5bfb2ee10e..cd367aa38a 100644 --- a/samples/ControlCatalog/Pages/LabelsPage.axaml +++ b/samples/ControlCatalog/Pages/LabelsPage.axaml @@ -1,13 +1,14 @@ - - + - + @@ -41,4 +42,4 @@ - + diff --git a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs index f3a7647f8c..b26112b351 100644 --- a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs +++ b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.Models; namespace ControlCatalog.Pages { - public partial class LabelsPage : UserControl + public partial class LabelsPage : ContentPage { private Person? _person; diff --git a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml index 8cf3610b47..d12bbfc302 100644 --- a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml +++ b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml @@ -1,5 +1,6 @@ - @@ -24,4 +25,4 @@ - + diff --git a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml.cs b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml.cs index 2060790594..e3815238be 100644 --- a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class LayoutTransformControlPage : UserControl + public partial class LayoutTransformControlPage : ContentPage { public LayoutTransformControlPage() { diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index e3a706bfed..f73a4c18ed 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -1,6 +1,7 @@ - @@ -47,4 +48,4 @@ SelectionMode="{Binding SelectionMode^}" WrapSelection="{Binding WrapSelection}"/> - + diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs index 3dac437f3a..208b5cce4b 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class ListBoxPage : UserControl + public partial class ListBoxPage : ContentPage { public ListBoxPage() { diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index bbcc759ca7..d9fb5d81bf 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -1,7 +1,8 @@ - Exported menu fallback @@ -95,4 +96,4 @@ - + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index a07306b81d..56a229a594 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -4,7 +4,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class MenuPage : UserControl + public partial class MenuPage : ContentPage { public MenuPage() { diff --git a/samples/ControlCatalog/Pages/NativeEmbedPage.xaml b/samples/ControlCatalog/Pages/NativeEmbedPage.xaml index 89e636112c..71d622bae4 100644 --- a/samples/ControlCatalog/Pages/NativeEmbedPage.xaml +++ b/samples/ControlCatalog/Pages/NativeEmbedPage.xaml @@ -1,10 +1,11 @@ - @@ -65,4 +66,4 @@ - + diff --git a/samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs b/samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs index b9bac10f9a..1cd1f78cc0 100644 --- a/samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs @@ -7,7 +7,7 @@ using Avalonia.Platform; namespace ControlCatalog.Pages { - public partial class NativeEmbedPage : UserControl + public partial class NativeEmbedPage : ContentPage { public NativeEmbedPage() { diff --git a/samples/ControlCatalog/Pages/NavigationDemoPage.xaml b/samples/ControlCatalog/Pages/NavigationDemoPage.xaml index 4849b2d5b8..0f7536b6a3 100644 --- a/samples/ControlCatalog/Pages/NavigationDemoPage.xaml +++ b/samples/ControlCatalog/Pages/NavigationDemoPage.xaml @@ -1,11 +1,14 @@ - - - - - - - + + + + + + + + diff --git a/samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs index d65a43a6ad..7ca41ab893 100644 --- a/samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NavigationDemoPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class NavigationDemoPage : UserControl + public partial class NavigationDemoPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml b/samples/ControlCatalog/Pages/NotificationsPage.xaml index b4094d8a2e..2ae1db7d43 100644 --- a/samples/ControlCatalog/Pages/NotificationsPage.xaml +++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml @@ -1,8 +1,9 @@ - - + diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs index f3db564571..5ff67e10dc 100644 --- a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs @@ -5,7 +5,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class NotificationsPage : UserControl + public partial class NotificationsPage : ContentPage { private NotificationViewModel _viewModel; diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml index a112f79c02..dcc84c3ae8 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml @@ -1,8 +1,9 @@ - - + diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs index 379090830d..db64fba0ef 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs @@ -9,7 +9,7 @@ using MiniMvvm; namespace ControlCatalog.Pages { - public partial class NumericUpDownPage : UserControl + public partial class NumericUpDownPage : ContentPage { public NumericUpDownPage() { diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml b/samples/ControlCatalog/Pages/OpenGlPage.xaml index 0e557cc1ad..9119be05f5 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml @@ -1,7 +1,8 @@ - @@ -10,4 +11,4 @@ IsVisible="False" VerticalAlignment="Bottom" HorizontalAlignment="Right" Click="SnapshotClick">Snapshot - + diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index 09173e01a5..51dc4c2c72 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -11,7 +11,7 @@ using ControlCatalog.Pages.OpenGl; namespace ControlCatalog.Pages { - public partial class OpenGlPage : UserControl + public partial class OpenGlPage : ContentPage { public OpenGlPage() { diff --git a/samples/ControlCatalog/Pages/PipsPagerPage.xaml b/samples/ControlCatalog/Pages/PipsPagerPage.xaml index 54112daae0..aee21a7d01 100644 --- a/samples/ControlCatalog/Pages/PipsPagerPage.xaml +++ b/samples/ControlCatalog/Pages/PipsPagerPage.xaml @@ -1,11 +1,14 @@ - - - - - - - + + + + + + + + diff --git a/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs b/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs index 0559a265e4..5a3b142f59 100644 --- a/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class PipsPagerPage : UserControl + public partial class PipsPagerPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml index 22c47f6bef..f86d53b740 100644 --- a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml +++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml @@ -1,4 +1,4 @@ - @@ -53,4 +54,4 @@ - + diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs index 5066bc51de..db1bbef40c 100644 --- a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class PlatformInfoPage : UserControl + public partial class PlatformInfoPage : ContentPage { public PlatformInfoPage() { diff --git a/samples/ControlCatalog/Pages/PointersPage.xaml b/samples/ControlCatalog/Pages/PointersPage.xaml index dda85316f6..2f958b901b 100644 --- a/samples/ControlCatalog/Pages/PointersPage.xaml +++ b/samples/ControlCatalog/Pages/PointersPage.xaml @@ -1,12 +1,12 @@ - - - + - - + + - - + + - - + + Capture 2 - - - + + diff --git a/samples/ControlCatalog/Pages/PointersPage.xaml.cs b/samples/ControlCatalog/Pages/PointersPage.xaml.cs index adf45029ec..388c8fa382 100644 --- a/samples/ControlCatalog/Pages/PointersPage.xaml.cs +++ b/samples/ControlCatalog/Pages/PointersPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Input; namespace ControlCatalog.Pages; -public partial class PointersPage : UserControl +public partial class PointersPage : TabbedPage { public PointersPage() { diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml index 52e1e198b5..3b80b5a6bb 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -1,4 +1,8 @@ - + A progress bar control @@ -18,10 +22,10 @@ - - @@ -36,4 +40,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs b/samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs index 42b9fe29d7..244161a61e 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class ProgressBarPage : UserControl + public partial class ProgressBarPage : ContentPage { public ProgressBarPage() { diff --git a/samples/ControlCatalog/Pages/RadioButtonPage.xaml b/samples/ControlCatalog/Pages/RadioButtonPage.xaml index 8623d81b3f..efb63ea124 100644 --- a/samples/ControlCatalog/Pages/RadioButtonPage.xaml +++ b/samples/ControlCatalog/Pages/RadioButtonPage.xaml @@ -1,5 +1,6 @@ - Allows the selection of a single option of many @@ -37,4 +38,4 @@ - + diff --git a/samples/ControlCatalog/Pages/RadioButtonPage.xaml.cs b/samples/ControlCatalog/Pages/RadioButtonPage.xaml.cs index 798df9fe77..8199a7caf5 100644 --- a/samples/ControlCatalog/Pages/RadioButtonPage.xaml.cs +++ b/samples/ControlCatalog/Pages/RadioButtonPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class RadioButtonPage : UserControl + public partial class RadioButtonPage : ContentPage { public RadioButtonPage() { diff --git a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml index f6ea26a84c..06f54dd28a 100644 --- a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml +++ b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml @@ -1,4 +1,4 @@ - - + diff --git a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs index a710cd7e5c..2c886fa9de 100644 --- a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs +++ b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class RefreshContainerPage : UserControl + public partial class RefreshContainerPage : ContentPage { private RefreshContainerViewModel _viewModel; diff --git a/samples/ControlCatalog/Pages/RelativePanelPage.axaml b/samples/ControlCatalog/Pages/RelativePanelPage.axaml index a6647b0d36..ccc39befd4 100644 --- a/samples/ControlCatalog/Pages/RelativePanelPage.axaml +++ b/samples/ControlCatalog/Pages/RelativePanelPage.axaml @@ -1,8 +1,9 @@ - @@ -60,4 +61,4 @@ - + diff --git a/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs index aec13a18e3..3c59deb791 100644 --- a/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs +++ b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs @@ -2,7 +2,7 @@ namespace ControlCatalog.Pages { - public partial class RelativePanelPage : UserControl + public partial class RelativePanelPage : ContentPage { public RelativePanelPage() { diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 66e90c2b34..9791152923 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -11,7 +11,7 @@ using Avalonia.Threading; namespace ControlCatalog.Pages { - public class ScreenPage : UserControl + public class ScreenPage : ContentPage { private double _leftMost; private double _topMost; diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml index f931542a04..0567dbe451 100644 --- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml +++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml @@ -1,10 +1,10 @@ - - - + - - + + - - - + + diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs index 7c684357d3..cfdd5bab54 100644 --- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs @@ -92,7 +92,7 @@ namespace ControlCatalog.Pages public List AvailableSnapPointsAlignment { get; } } - public partial class ScrollViewerPage : UserControl + public partial class ScrollViewerPage : TabbedPage { public ScrollViewerPage() { diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml index 7821e885ba..445564cb54 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml +++ b/samples/ControlCatalog/Pages/SliderPage.xaml @@ -1,5 +1,6 @@ - @@ -80,4 +81,4 @@ - + diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml.cs b/samples/ControlCatalog/Pages/SliderPage.xaml.cs index 8345df8716..f65f714300 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml.cs +++ b/samples/ControlCatalog/Pages/SliderPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class SliderPage : UserControl + public partial class SliderPage : ContentPage { public SliderPage() { diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml b/samples/ControlCatalog/Pages/SplitViewPage.xaml index 201765ad88..144688e358 100644 --- a/samples/ControlCatalog/Pages/SplitViewPage.xaml +++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml @@ -1,8 +1,9 @@ - @@ -109,4 +110,4 @@ - + diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs b/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs index b711db3c49..4917a13ccf 100644 --- a/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs +++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class SplitViewPage : UserControl + public partial class SplitViewPage : ContentPage { public SplitViewPage() { diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index a3bacfd92a..e7aa18ea84 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -1,7 +1,8 @@ - @@ -108,4 +109,4 @@ - + diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs index b7f34eda86..366b5216ef 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -6,7 +6,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class TabControlPage : UserControl + public partial class TabControlPage : ContentPage { public TabControlPage() { diff --git a/samples/ControlCatalog/Pages/TabStripPage.xaml b/samples/ControlCatalog/Pages/TabStripPage.xaml index b576beeb5f..f4a47af353 100644 --- a/samples/ControlCatalog/Pages/TabStripPage.xaml +++ b/samples/ControlCatalog/Pages/TabStripPage.xaml @@ -1,6 +1,7 @@ - @@ -31,4 +32,4 @@ - + diff --git a/samples/ControlCatalog/Pages/TabStripPage.xaml.cs b/samples/ControlCatalog/Pages/TabStripPage.xaml.cs index d2873f6586..d830109833 100644 --- a/samples/ControlCatalog/Pages/TabStripPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabStripPage.xaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class TabStripPage : UserControl + public partial class TabStripPage : ContentPage { public TabStripPage() { diff --git a/samples/ControlCatalog/Pages/TabbedDemoPage.xaml b/samples/ControlCatalog/Pages/TabbedDemoPage.xaml index bb1467060b..58a3b4fe82 100644 --- a/samples/ControlCatalog/Pages/TabbedDemoPage.xaml +++ b/samples/ControlCatalog/Pages/TabbedDemoPage.xaml @@ -1,11 +1,14 @@ - - - - - - - + + + + + + + + diff --git a/samples/ControlCatalog/Pages/TabbedDemoPage.xaml.cs b/samples/ControlCatalog/Pages/TabbedDemoPage.xaml.cs index 51da6330a1..88692c97f3 100644 --- a/samples/ControlCatalog/Pages/TabbedDemoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabbedDemoPage.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class TabbedDemoPage : UserControl + public partial class TabbedDemoPage : ContentPage { private static readonly (string Group, string Title, string Description, Func Factory)[] Demos = { diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index 7c0300d318..3418cffe74 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -1,5 +1,6 @@ - A control that can display text @@ -137,4 +138,4 @@ - + diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml.cs b/samples/ControlCatalog/Pages/TextBlockPage.xaml.cs index d097d1d083..d10d255a4f 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class TextBlockPage : UserControl + public partial class TextBlockPage : ContentPage { public TextBlockPage() { diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 346e561dfc..9b5a412f86 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -1,6 +1,7 @@ - @@ -101,4 +102,4 @@ FontFamily="avares://ControlCatalog/Assets/Fonts#WenQuanYi Micro Hei" Text="计算机科学(是系统性研究信息与计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。它通常被形容为对那些创造、描述以及转换信息的算法处理的系统研究。计算机科学包含很多分支领域;有些强调特定结果的计算,比如计算机图形学;而有些是探討计算问题的性质,比如计算复杂性理论;还有一些领域專注于怎样实现计算,比如程式語言理論是研究描述计算的方法,而程式设计是应用特定的程式語言解决特定的计算问题,人机交互则是專注于怎样使计算机和计算变得有用、好用,以及随时随地为人所用。 有时公众会误以为计算机科学就是解决计算机问题的事业(比如信息技术),或者只是与使用计算机的经验有关,如玩游戏、上网或者文字处理。其实计算机科学所关注的,不仅仅是去理解实现类似游戏、浏览器这些软件的程序的性质,更要通过现有的知识创造新的程序或者改进已有的程序。" /> - + diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml.cs b/samples/ControlCatalog/Pages/TextBoxPage.xaml.cs index 035f951a6b..3c38a05c53 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class TextBoxPage : UserControl + public partial class TextBoxPage : ContentPage { public TextBoxPage() { diff --git a/samples/ControlCatalog/Pages/ThemePage.axaml b/samples/ControlCatalog/Pages/ThemePage.axaml index 7eb95471a0..ccc5769fe7 100644 --- a/samples/ControlCatalog/Pages/ThemePage.axaml +++ b/samples/ControlCatalog/Pages/ThemePage.axaml @@ -1,13 +1,14 @@ - - + @@ -52,7 +53,7 @@ - + - + diff --git a/samples/ControlCatalog/Pages/ThemePage.axaml.cs b/samples/ControlCatalog/Pages/ThemePage.axaml.cs index d355b399a4..4f45e93ed0 100644 --- a/samples/ControlCatalog/Pages/ThemePage.axaml.cs +++ b/samples/ControlCatalog/Pages/ThemePage.axaml.cs @@ -4,7 +4,7 @@ using Avalonia.Styling; namespace ControlCatalog.Pages { - public partial class ThemePage : UserControl + public partial class ThemePage : ContentPage { public static ThemeVariant Pink { get; } = new("Pink", ThemeVariant.Light); diff --git a/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml b/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml index 6afe6dd135..126cc7e7d2 100644 --- a/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml +++ b/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml @@ -1,5 +1,6 @@ - @@ -17,7 +18,7 @@ + Text="<ToggleSwitch>headered</ToggleSwitch>"/> @@ -26,14 +27,14 @@ + OnContent="On" + OffContent="Off" + Margin="10"/> - + @@ -55,7 +56,7 @@ ContentOff="Off" />" - + - - + + diff --git a/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml.cs b/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml.cs index 77d77daeed..a60eb295b0 100644 --- a/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ToggleSwitchPage.xaml.cs @@ -2,7 +2,7 @@ namespace ControlCatalog.Pages { - public partial class ToggleSwitchPage : UserControl + public partial class ToggleSwitchPage : ContentPage { public ToggleSwitchPage() { diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index 8b76cccbc9..cc3a50b8e9 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -1,5 +1,6 @@ - @@ -10,7 +11,7 @@ HorizontalAlignment="Center"> - + diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml.cs b/samples/ControlCatalog/Pages/ToolTipPage.xaml.cs index cd0b5c72a8..e436407dc5 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml.cs @@ -6,7 +6,7 @@ using Avalonia.Interactivity; namespace ControlCatalog.Pages { - public partial class ToolTipPage : UserControl + public partial class ToolTipPage : ContentPage { public ToolTipPage() { diff --git a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml index 03ef86fb61..5435ed278d 100644 --- a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml +++ b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml @@ -1,19 +1,20 @@ - - + - + - + - + - + 8 - + @@ -81,4 +82,4 @@ - + diff --git a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml.cs b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml.cs index 863ddae6e0..818ab6219d 100644 --- a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml.cs +++ b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class TransitioningContentControlPage : UserControl + public partial class TransitioningContentControlPage : ContentPage { public TransitioningContentControlPage() { diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml index 2e3fca58cb..88dc65b5f2 100644 --- a/samples/ControlCatalog/Pages/TreeViewPage.xaml +++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml @@ -1,6 +1,7 @@ - @@ -32,4 +33,4 @@ - + diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs b/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs index c43c2eb49e..d03590febd 100644 --- a/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml.cs @@ -3,7 +3,7 @@ using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { - public partial class TreeViewPage : UserControl + public partial class TreeViewPage : ContentPage { public TreeViewPage() { diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml index e66655253c..3848e405db 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml @@ -1,6 +1,7 @@ - @@ -50,4 +51,4 @@ - + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs index c140cb9857..f7b1ea3f75 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; namespace ControlCatalog.Pages { - public partial class ViewboxPage : UserControl + public partial class ViewboxPage : ContentPage { public ViewboxPage() { diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index df84c0bd6b..0017ee09b5 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -1,9 +1,10 @@ - @@ -50,4 +51,4 @@ - + diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs index d62c7ec10f..2ee8677eba 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs @@ -2,7 +2,7 @@ namespace ControlCatalog.Pages { - public partial class WindowCustomizationsPage : UserControl + public partial class WindowCustomizationsPage : ContentPage { public WindowCustomizationsPage() { diff --git a/samples/ControlCatalog/ScrollPage.xaml b/samples/ControlCatalog/ScrollPage.xaml new file mode 100644 index 0000000000..6d5ecb815e --- /dev/null +++ b/samples/ControlCatalog/ScrollPage.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index ce9209f61a..0d877b253f 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -8,7 +8,7 @@ using MiniMvvm; namespace ControlCatalog.ViewModels { - class MainWindowViewModel : ViewModelBase + partial class MainWindowViewModel : ViewModelBase { private WindowState _windowState; private WindowState[] _windowStates = Array.Empty(); @@ -20,6 +20,7 @@ namespace ControlCatalog.ViewModels private bool _canResize; private bool _canMinimize; private bool _canMaximize; + private int _selectedDecorationIndex; public MainWindowViewModel() { @@ -51,6 +52,8 @@ namespace ControlCatalog.ViewModels CanResize = true; CanMinimize = true; CanMaximize = true; + + Filter(); } public bool ExtendClientAreaEnabled @@ -113,6 +116,12 @@ namespace ControlCatalog.ViewModels set { RaiseAndSetIfChanged(ref _canMaximize, value); } } + public int SelectedDecorationIndex + { + get { return _selectedDecorationIndex; } + set { RaiseAndSetIfChanged(ref _selectedDecorationIndex, value); } + } + public MiniCommand AboutCommand { get; } diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel_PageList.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel_PageList.cs new file mode 100644 index 0000000000..4c85f64765 --- /dev/null +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel_PageList.cs @@ -0,0 +1,196 @@ +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Dialogs; +using System; +using System.ComponentModel.DataAnnotations; +using Avalonia; +using MiniMvvm; +using Avalonia.Collections; +using ControlCatalog.Pages; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; + +namespace ControlCatalog.ViewModels +{ + partial class MainWindowViewModel + { + private int _selectedPageIndex; + private bool _isDrawerOpened = true; + private bool _ignoreListChange = false; + private string? _query = ""; + private PageItem? _currentItem; + private SplitViewDisplayMode _displayMode; + + private List _items = new() + { + new PageItem("Composition", () => new CompositionPage(), Icons.Layers), + new PageItem("Accelerator", () => new AcceleratorPage(), Icons.Keyboard), + new PageItem("Acrylic", () => new AcrylicPage(), Icons.Blur), + new PageItem("AdornerLayer", () => new AdornerLayerPage(), Icons.Sparkle), + new PageItem("AutoCompleteBox",() => new AutoCompleteBoxPage(), Icons.TextInput), + new PageItem("Border",() => new BorderPage(), Icons.Border), + new PageItem("BitmapCache",() => new BitmapCachePage(), Icons.Lightning), + new PageItem("Buttons",() => new ButtonsPage(), Icons.CursorClick), + new PageItem("ButtonSpinner",() => new ButtonSpinnerPage(), Icons.Spinner), + new PageItem("Calendar",() => new CalendarPage(), Icons.Calendar), + new PageItem("Canvas",() => new CanvasPage(), Icons.Canvas), + new PageItem("CommandBar",() => new CommandBarPage(), Icons.Terminal), + new PageItem("Carousel",() => new Pages.CarouselPage(), Icons.Slides), + new PageItem("CarouselPage",() => new CarouselDemoPage(), Icons.Slides), + new PageItem("CheckBox",() => new CheckBoxPage(), Icons.Checkbox), + new PageItem("Clipboard",() => new ClipboardPage(), Icons.Clipboard), + new PageItem("ColorPicker",() => new ColorPickerPage(), Icons.Palette), + new PageItem("ComboBox",() => new ComboBoxPage(), Icons.Dropdown), + new PageItem("Container Queries",() => new ContainerQueryPage(), Icons.Container), + new PageItem("ContentPage",() => new ContentDemoPage(), Icons.Document), + new PageItem("ContextFlyout",() => new ContextFlyoutPage(), Icons.Menu), + new PageItem("ContextMenu",() => new ContextMenuPage(), Icons.Menu), + new PageItem("Cursor",() => new CursorPage(), Icons.Cursor), + new PageItem("Custom Drawing",() => new CustomDrawing(), Icons.Brush), + new PageItem("DataGrid",() => new DataGridPage(), Icons.Grid), + new PageItem("Data Validation",() => new DataValidationPage(), Icons.Shield), + new PageItem("Date/Time Picker",() => new DateTimePickerPage(), Icons.Clock), + new PageItem("CalendarDatePicker",() => new CalendarDatePickerPage(), Icons.Calendar), + new PageItem("Dialogs",() => new DialogsPage(), Icons.Dialog), + new PageItem("Drag+Drop",() => new DragAndDropPage(), Icons.DragDrop), + new PageItem("DrawerPage",() => new DrawerDemoPage(), Icons.Drawer), + new PageItem("Expander",() => new ExpanderPage(), Icons.Expand), + new PageItem("Flyouts",() => new FlyoutsPage(), Icons.Flyout), + new PageItem("Focus",() => new FocusPage(), Icons.Target), + new PageItem("Gestures",() => new GesturePage(), Icons.Gesture), + new PageItem("Image",() => new ImagePage(), Icons.Image), + new PageItem("Label",() => new LabelsPage(), Icons.Tag), + new PageItem("LayoutTransformControl",() => new LayoutTransformControlPage(), Icons.Transform), + new PageItem("ListBox",() => new ListBoxPage(), Icons.List), + new PageItem("Menu",() => new MenuPage(), Icons.Menu), + new PageItem("NavigationPage",() => new NavigationDemoPage(), Icons.Navigation), + new PageItem("Notifications",() => new NotificationsPage(), Icons.Bell), + new PageItem("NumericUpDown",() => new NumericUpDownPage(), Icons.Number), + new PageItem("OpenGL",() => new OpenGlPage(), Icons.Cube3D), + new PageItem("OpenGL Lease",() => new OpenGlLeasePage(), Icons.Cube3D), + new PageItem("PipsPager",() => new PipsPagerPage(), Icons.HorizontalDots), + new PageItem("Platform Information",() => new PlatformInfoPage(), Icons.Info), + new PageItem("Pointers",() => new PointersPage(), Icons.Cursor), + new PageItem("ProgressBar",() => new ProgressBarPage(), Icons.Progress), + new PageItem("RadioButton",() => new RadioButtonPage(), Icons.Radio), + new PageItem("RefreshContainer",() => new RefreshContainerPage(), Icons.Refresh), + new PageItem("RelativePanel",() => new RelativePanelPage(), Icons.Layout), + new PageItem("ScrollViewer",() => new ScrollViewerPage(), Icons.Scroll), + new PageItem("Slider",() => new SliderPage(), Icons.Tune), + new PageItem("SplitView",() => new SplitViewPage(), Icons.Split), + new PageItem("TabbedPage",() => new TabbedDemoPage(), Icons.Tab), + new PageItem("TabControl",() => new TabControlPage(), Icons.Tab), + new PageItem("TabStrip",() => new TabStripPage(), Icons.Tab), + new PageItem("TextBox",() => new TextBoxPage(), Icons.TextInput), + new PageItem("TextBlock",() => new TextBlockPage(), Icons.TextInput), + new PageItem("Theme Variants",() => new ThemePage(), Icons.Theme), + new PageItem("ToggleSwitch",() => new ToggleSwitchPage(), Icons.Toggle), + new PageItem("ToolTip",() => new ToolTipPage(), Icons.Tooltip), + new PageItem("TransitioningContentControl",() => new TransitioningContentControlPage(), Icons.Transition), + new PageItem("TreeView",() => new TreeViewPage(), Icons.Tree), + new PageItem("Viewbox",() => new ViewboxPage(), Icons.Viewbox), + new PageItem("Native Embed",() => new NativeEmbedPage(), Icons.Puzzle), + new PageItem("Window Customizations",() => new WindowCustomizationsPage(), Icons.Window), + new PageItem("HeaderedContentControl",() => new HeaderedContentPage(), Icons.Header), + new PageItem("Screens",() => new ScreenPage(), Icons.Monitor), + }; + + public AvaloniaList Pages { get; } = new AvaloniaList(); + + public void Filter(string? query = "") + { + try + { + _ignoreListChange = true; + Pages.Clear(); + + if (!string.IsNullOrWhiteSpace(query)) + Pages.AddRange(_items.Where(x => x.Header.Contains(query))); + else + Pages.AddRange(_items); + } + finally + { + _ignoreListChange = false; + if (_currentItem != null) + { + var newIndex = Pages.IndexOf(_currentItem); + if (newIndex != -1) + { + SelectedPageIndex = newIndex; + } + } + } + } + + public INavigation? Navigator { get; internal set; } + + public int SelectedPageIndex + { + get { return _selectedPageIndex; } + set + { + RaiseAndSetIfChanged(ref _selectedPageIndex, value); + + if (!_ignoreListChange) + { + NavigateTo(_selectedPageIndex); + + if (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay) + IsDrawerOpened = false; + } + } + } + + public bool IsDrawerOpened + { + get { return _isDrawerOpened; } + set { RaiseAndSetIfChanged(ref _isDrawerOpened, value); } + } + + public SplitViewDisplayMode DisplayMode + { + get { return _displayMode; } + set { RaiseAndSetIfChanged(ref _displayMode, value); } + } + + public string? Query + { + get { return _query; } + set + { + RaiseAndSetIfChanged(ref _query, value); + + Filter(value); + } + } + + private async void NavigateTo(int pageIndex) + { + if (pageIndex < 0 || pageIndex >= Pages.Count || Navigator is null) + return; + + var item = Pages[pageIndex]; + + if (item != null) + { + var view = item.Factory(); + if (view is Page page && view.GetType() != Navigator.NavigationStack.LastOrDefault()?.GetType()) + { + _currentItem = item; + await Navigator.ReplaceAsync(page); + } + } + } + } + + class PageItem(string header, Func factory, string? iconData = null) + { + public string Header { get; } = header; + public Func Factory { get; } = factory; + public string? IconData { get; } = iconData; + + public bool IsVisible { get; set; } = true; + } +} diff --git a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml index d9b3e3c73f..b4d9900617 100644 --- a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml +++ b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml @@ -33,8 +33,8 @@ #FFFFFFFF #FFF2F2F2 - - + + 40 220 36 @@ -134,14 +134,14 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml index a3395ad99f..2a817ccfcb 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml @@ -10,7 +10,6 @@ - diff --git a/src/Avalonia.Themes.Simple/Controls/PathIcon.xaml b/src/Avalonia.Themes.Simple/Controls/PathIcon.xaml index 9e39ba4645..9e451cf966 100644 --- a/src/Avalonia.Themes.Simple/Controls/PathIcon.xaml +++ b/src/Avalonia.Themes.Simple/Controls/PathIcon.xaml @@ -3,7 +3,6 @@ x:ClassModifier="internal"> - diff --git a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs index 371cbe4d6d..e368d93892 100644 --- a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs @@ -3,9 +3,14 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Styling; +using Avalonia.Themes.Simple; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -118,6 +123,28 @@ public class CommandBarButtonTests : ScopedTestBase var btn = new CommandBarButton { CommandParameter = "param" }; Assert.Equal("param", btn.CommandParameter); } + + [Fact] + public void Foreground_DoesNotSetOrOverwriteIconElementForeground() + { + var icon = new PathIcon(); + var btn = new CommandBarButton + { + Icon = icon, + Foreground = Brushes.Red + }; + + Assert.False(icon.IsSet(TemplatedControl.ForegroundProperty)); + + btn.Foreground = Brushes.Blue; + + Assert.False(icon.IsSet(TemplatedControl.ForegroundProperty)); + + icon.Foreground = Brushes.Green; + btn.Foreground = Brushes.Red; + + Assert.Same(Brushes.Green, icon.Foreground); + } } public class CommandBarToggleButtonTests : ScopedTestBase @@ -205,6 +232,28 @@ public class CommandBarToggleButtonTests : ScopedTestBase var btn = new CommandBarToggleButton { CommandParameter = 42 }; Assert.Equal(42, btn.CommandParameter); } + + [Fact] + public void Foreground_DoesNotSetOrOverwriteIconElementForeground() + { + var icon = new PathIcon(); + var btn = new CommandBarToggleButton + { + Icon = icon, + Foreground = Brushes.Red + }; + + Assert.False(icon.IsSet(TemplatedControl.ForegroundProperty)); + + btn.Foreground = Brushes.Blue; + + Assert.False(icon.IsSet(TemplatedControl.ForegroundProperty)); + + icon.Foreground = Brushes.Green; + btn.Foreground = Brushes.Red; + + Assert.Same(Brushes.Green, icon.Foreground); + } } public class CommandBarSeparatorTests : ScopedTestBase @@ -309,6 +358,54 @@ public class CommandBarDefaultsTests : ScopedTestBase public void Content_DefaultIsNull() => Assert.Null(new CommandBar().Content); + [Fact] + public void Foreground_IsInheritedByCommandBarButtonPathIconThroughThemeTemplate() + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow); + + var icon = new PathIcon(); + var btn = new CommandBarButton + { + Icon = icon + }; + var commandBar = new CommandBar + { + Foreground = Brushes.Red + }; + commandBar.PrimaryCommands.Add(btn); + + Assert.Same(icon, ApplySimpleThemeAndGetPresentedPathIcon(commandBar, btn)); + Assert.Same(Brushes.Red, icon.Foreground); + + commandBar.Foreground = Brushes.Blue; + + Assert.Same(Brushes.Blue, icon.Foreground); + } + + [Fact] + public void Foreground_IsInheritedByCommandBarToggleButtonPathIconThroughThemeTemplate() + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow); + + var icon = new PathIcon(); + var btn = new CommandBarToggleButton + { + Icon = icon + }; + var commandBar = new CommandBar + { + Foreground = Brushes.Red + }; + commandBar.PrimaryCommands.Add(btn); + + Assert.Same(icon, ApplySimpleThemeAndGetPresentedPathIcon(commandBar, btn)); + Assert.Same(Brushes.Red, icon.Foreground); + + commandBar.Foreground = Brushes.Blue; + + Assert.Same(Brushes.Blue, icon.Foreground); + } + [Fact] public void PrimaryCommands_NotNull() => Assert.NotNull(new CommandBar().PrimaryCommands); @@ -370,6 +467,46 @@ public class CommandBarDefaultsTests : ScopedTestBase [Fact] public void ItemWidthCollapsed_DefaultIs42() => Assert.Equal(42d, new CommandBar().ItemWidthCollapsed); + + private static PathIcon ApplySimpleThemeAndGetPresentedPathIcon(CommandBar commandBar, TemplatedControl command) + { + var simpleTheme = new SimpleTheme(); + Assert.True(simpleTheme.TryGetResource(typeof(CommandBar), ThemeVariant.Default, out var commandBarTheme)); + Assert.True(simpleTheme.TryGetResource(command.GetType(), ThemeVariant.Default, out var commandTheme)); + commandBar.Theme = Assert.IsType(commandBarTheme); + command.Theme = Assert.IsType(commandTheme); + + var root = new TestRoot + { + Width = 500, + Height = 200, + Child = commandBar, + Styles = + { + simpleTheme + } + }; + + root.ApplyStyling(); + commandBar.ApplyStyling(); + commandBar.ApplyTemplate(); + root.LayoutManager.ExecuteInitialLayoutPass(); + + command.ApplyStyling(); + command.ApplyTemplate(); + + var presenter = command.GetTemplateChildren() + .OfType() + .Single(x => x.Name == "PART_IconPresenter"); + + presenter.ApplyStyling(); + presenter.UpdateChild(); + + var pathIcon = Assert.IsType(presenter.Child); + pathIcon.ApplyStyling(); + + return pathIcon; + } } public class CommandBarPropertyRoundTripTests : ScopedTestBase From dd9217ee9db26db057bcccd0ca7f9dcd567b1fb2 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 27 Apr 2026 09:47:48 +0200 Subject: [PATCH 71/86] Win32: fix BorderOnly maximized position on secondary screens in more cases (#21229) * Update IntegrationTests.Win32 to test all screens * Win32: use GetWindowPlacement to determine the screen to maximize to --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 6 ++-- .../ExtendClientAreaWindowTests.cs | 35 +++++++++++-------- .../StandardWindowTests.cs | 34 +++++++++--------- .../UnmanagedMethods.cs | 5 +++ .../WindowExtensions.cs | 8 ++--- 5 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index f003da9296..d9be50a967 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -852,9 +852,11 @@ namespace Avalonia.Win32 !_isFullScreenActive && !flags.HasAllFlags(SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE)) { - // Prefer ScreenFromRect as it contains the new position. + GetWindowPlacement(Hwnd, out var placement); + + // Prefer ScreenFromRect with the window's restored bounds. // If the window was minimized, ScreenFromHwnd won't return the correct monitor at this point. - var screen = Screen.ScreenFromRect(new PixelRect(pos->x, pos->y, pos->cx, pos->cy)) + var screen = Screen.ScreenFromRect(placement.NormalPosition.ToPixelRect()) ?? Screen.ScreenFromHwnd(Hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); if (screen is not null) diff --git a/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs b/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs index 7c7d34ac14..2e94734d17 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ExtendClientAreaWindowTests.cs @@ -3,17 +3,17 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Automation; using Avalonia.Controls; -using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.VisualTree; using Xunit; +using static Avalonia.IntegrationTests.Win32.UnmanagedMethods; namespace Avalonia.IntegrationTests.Win32; public abstract class ExtendClientAreaWindowTests : IDisposable { - private const double ClientWidth = 200; - private const double ClientHeight = 200; + private const int ClientWidth = 200; + private const int ClientHeight = 200; private Window? _window; @@ -28,10 +28,13 @@ public abstract class ExtendClientAreaWindowTests : IDisposable protected abstract WindowDecorations Decorations { get; } - public static MatrixTheoryData States - => new([true, false], Enum.GetValues()); + public static MatrixTheoryData States + => new( + Enumerable.Range(0, GetSystemMetrics(SM_CMONITORS)), + Enum.GetValues(), + [true, false]); - private async Task InitWindowAsync(WindowState state, bool canResize) + private async Task InitWindowAsync(int screenIndex, WindowState state, bool canResize) { Assert.Null(_window); @@ -44,7 +47,6 @@ public abstract class ExtendClientAreaWindowTests : IDisposable Width = ClientWidth, Height = ClientHeight, WindowStartupLocation = WindowStartupLocation.Manual, - Position = new PixelPoint(50, 50), Content = new Border { Background = Brushes.DodgerBlue, @@ -53,6 +55,9 @@ public abstract class ExtendClientAreaWindowTests : IDisposable } }; + var screenCenter = _window.Screens.All[screenIndex].Bounds.Center; + _window.Position = new PixelPoint(screenCenter.X - ClientWidth / 2, screenCenter.Y - ClientHeight / 2); + _window.Show(); await Window.WhenLoadedAsync(); @@ -60,9 +65,9 @@ public abstract class ExtendClientAreaWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task Normal_State_Respects_Client_Size(bool canResize, WindowState initialState) + public async Task Normal_State_Respects_Client_Size(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.Normal) Window.WindowState = WindowState.Normal; @@ -79,16 +84,16 @@ public abstract class ExtendClientAreaWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task Maximized_State_Fills_Screen_Working_Area(bool canResize, WindowState initialState) + public async Task Maximized_State_Fills_Screen_Working_Area(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.Maximized) Window.WindowState = WindowState.Maximized; // The client size should match the screen working area var clientSize = Window.GetWin32ClientSize(); - var screenWorkingArea = Window.GetScreen().WorkingArea; + var screenWorkingArea = Window.GetScreenAtIndex(screenIndex).WorkingArea; Assert.Equal(screenWorkingArea.Size, clientSize); VerifyMaximizedState(); @@ -98,16 +103,16 @@ public abstract class ExtendClientAreaWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task FullScreen_State_Fills_Screen(bool canResize, WindowState initialState) + public async Task FullScreen_State_Fills_Screen(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.FullScreen) Window.WindowState = WindowState.FullScreen; // The client size should match the screen bounds var clientSize = Window.GetWin32ClientSize(); - var screenBounds = Window.GetScreen().Bounds; + var screenBounds = Window.GetScreenAtIndex(screenIndex).Bounds; Assert.Equal(screenBounds.Width, clientSize.Width); Assert.Equal(screenBounds.Height, clientSize.Height); diff --git a/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs b/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs index 5971c4dc6c..233f69122b 100644 --- a/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/StandardWindowTests.cs @@ -2,19 +2,16 @@ using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Controls.Chrome; -using Avalonia.Interactivity; using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.VisualTree; using Xunit; +using static Avalonia.IntegrationTests.Win32.UnmanagedMethods; namespace Avalonia.IntegrationTests.Win32; public abstract class StandardWindowTests : IDisposable { - private const double ClientWidth = 200; - private const double ClientHeight = 200; + private const int ClientWidth = 200; + private const int ClientHeight = 200; private Window? _window; @@ -31,10 +28,13 @@ public abstract class StandardWindowTests : IDisposable protected abstract bool HasCaption { get; } - public static MatrixTheoryData States - => new([true, false], Enum.GetValues()); + public static MatrixTheoryData States + => new( + Enumerable.Range(0, GetSystemMetrics(SM_CMONITORS)), + Enum.GetValues(), + [true, false]); - private async Task InitWindowAsync(WindowState state, bool canResize) + private async Task InitWindowAsync(int screenIndex, WindowState state, bool canResize) { Assert.Null(_window); @@ -47,7 +47,6 @@ public abstract class StandardWindowTests : IDisposable Width = ClientWidth, Height = ClientHeight, WindowStartupLocation = WindowStartupLocation.Manual, - Position = new PixelPoint(50, 50), Content = new Border { Background = Brushes.DodgerBlue, @@ -56,6 +55,9 @@ public abstract class StandardWindowTests : IDisposable } }; + var screenCenter = _window.Screens.All[screenIndex].Bounds.Center; + _window.Position = new PixelPoint(screenCenter.X - ClientWidth / 2, screenCenter.Y - ClientHeight / 2); + _window.Show(); await Window.WhenLoadedAsync(); @@ -63,16 +65,16 @@ public abstract class StandardWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task Maximized_State_Fills_Screen_Working_Area(bool canResize, WindowState initialState) + public async Task Maximized_State_Fills_Screen_Working_Area(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.Maximized) Window.WindowState = WindowState.Maximized; // The client size should match the screen working area var clientSize = Window.GetWin32ClientSize(); - var screenWorkingArea = Window.GetScreen().WorkingArea; + var screenWorkingArea = Window.GetScreenAtIndex(screenIndex).WorkingArea; if (HasCaption) { @@ -85,16 +87,16 @@ public abstract class StandardWindowTests : IDisposable [Theory] [MemberData(nameof(States))] - public async Task FullScreen_State_Fills_Screen(bool canResize, WindowState initialState) + public async Task FullScreen_State_Fills_Screen(int screenIndex, WindowState initialState, bool canResize) { - await InitWindowAsync(initialState, canResize); + await InitWindowAsync(screenIndex, initialState, canResize); if (initialState != WindowState.FullScreen) Window.WindowState = WindowState.FullScreen; // The client size should match the screen bounds var clientSize = Window.GetWin32ClientSize(); - var screenBounds = Window.GetScreen().Bounds; + var screenBounds = Window.GetScreenAtIndex(screenIndex).Bounds; Assert.Equal(screenBounds.Width, clientSize.Width); Assert.Equal(screenBounds.Height, clientSize.Height); diff --git a/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs b/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs index 591ed182c4..4f4f4c03d0 100644 --- a/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs +++ b/tests/Avalonia.IntegrationTests.Win32/UnmanagedMethods.cs @@ -13,6 +13,9 @@ internal static partial class UnmanagedMethods [return: MarshalAs(UnmanagedType.Bool)] public static partial bool GetWindowRect(IntPtr hwnd, out RECT lpRect); + [LibraryImport("user32.dll")] + public static partial int GetSystemMetrics(int nIndex); + public struct RECT { public int left; @@ -20,4 +23,6 @@ internal static partial class UnmanagedMethods public int right; public int bottom; } + + public const int SM_CMONITORS = 80; } diff --git a/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs b/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs index 1d92324961..acf154ddde 100644 --- a/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Win32/WindowExtensions.cs @@ -27,12 +27,8 @@ internal static class WindowExtensions } } - public static Screen GetScreen(this Window window) - { - var screen = window.Screens.ScreenFromWindow(window); - Assert.NotNull(screen); - return screen; - } + public static Screen GetScreenAtIndex(this Window window, int index) + => window.Screens.All[index]; public static PixelSize GetWin32ClientSize(this Window window) { From 009d75af98d3b091f22e01aa8ef78657c092c932 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 27 Apr 2026 09:47:58 +0200 Subject: [PATCH 72/86] Fix FocusManager.SetFocusScope not doing anything (#21261) * Add failing test for SetFocusScope * Fix FocusManager.SetFocusScope not doing anything --- src/Avalonia.Base/Input/FocusManager.cs | 10 ++++++ .../Input/InputElement_Focus.cs | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs index 72a13d385d..5b046ab682 100644 --- a/src/Avalonia.Base/Input/FocusManager.cs +++ b/src/Avalonia.Base/Input/FocusManager.cs @@ -142,6 +142,9 @@ namespace Avalonia.Input [PrivateApi] public void SetFocusScope(IFocusScope scope) { + if (KeyboardDevice.Instance is not { } keyboardDevice) + return; + if (GetFocusedElement(scope) is { } focused) { Focus(focused); @@ -153,6 +156,13 @@ namespace Avalonia.Input // focus etc. Focus(scopeElement); } + else + { + // If the scope isn't focusable, make sure we still set it as the current focus root, + // otherwise it will be completely ignored + _focusRoot = scope as StyledElement; + keyboardDevice.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None, false); + } } [PrivateApi] diff --git a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs index 9e835b2427..580c80e79f 100644 --- a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs +++ b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs @@ -703,6 +703,41 @@ namespace Avalonia.Base.UnitTests.Input Assert.Same(innerButton, focusManager.GetFocusedElement()); } + // https://github.com/AvaloniaUI/Avalonia/issues/13134 + [Fact] + public void SetFocusScope_On_Non_Focusable_Scope_Changes_Scope() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + + Button outerButton; + TestFocusScope innerScope; + var root = new TestRoot + { + Child = new StackPanel + { + Focusable = false, + Children = + { + (innerScope = new TestFocusScope()), + (outerButton = new Button()) + } + } + }; + + outerButton.Focus(); + + var focusManager = Assert.IsType(root.FocusManager); + Assert.Same(outerButton, focusManager.GetFocusedElement()); + + // Switch to a scope that has no previously focused element and isn't focusable itself. + // TestFocusScope is a Panel (Focusable = false) + IFocusScope. + focusManager.SetFocusScope(innerScope); + + // Focus must be cleared: the scope is not focusable and has no prior focused element. + // Before the fix this was a no-op and outerButton would still be reported as focused. + Assert.Null(focusManager.GetFocusedElement()); + } + [Fact] public void Can_Get_First_Focusable_Element() { From db2b01bc01885283757c967dffc75f770b7ea41e Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 27 Apr 2026 12:32:52 +0200 Subject: [PATCH 73/86] Clarify that ElementRole is dependent on ExtendClientAreaToDecorationsHint (#21243) --- src/Avalonia.Controls/Chrome/WindowDecorationProperties.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/Chrome/WindowDecorationProperties.cs b/src/Avalonia.Controls/Chrome/WindowDecorationProperties.cs index 5ad874eec4..0881dda57f 100644 --- a/src/Avalonia.Controls/Chrome/WindowDecorationProperties.cs +++ b/src/Avalonia.Controls/Chrome/WindowDecorationProperties.cs @@ -12,6 +12,7 @@ public static class WindowDecorationProperties /// Marks a visual element with a specific role for non-client hit-testing. /// Can be applied to any element in the visual tree, not limited to decoration children. /// + /// Setting an element role only has an effect if is true. public static readonly AttachedProperty ElementRoleProperty = AvaloniaProperty.RegisterAttached("ElementRole", typeof(WindowDecorationProperties)); @@ -23,5 +24,6 @@ public static class WindowDecorationProperties /// /// Sets the for the specified element. /// + /// Setting an element role only has an effect if is true. public static void SetElementRole(Visual element, WindowDecorationsElementRole value) => element.SetValue(ElementRoleProperty, value); } From 65dc74c39f10b324bee7c1ed4c25b0b06ccd0910 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 27 Apr 2026 10:54:09 +0000 Subject: [PATCH 74/86] [ANDROID] Make ToplevelImpl._view nullable. (#21270) * mark ToplevelImpl._view as nullable. * Update src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs Co-authored-by: Max Katz * return 1 as default scaling in android TopLevelImpl --------- Co-authored-by: Max Katz --- src/Android/Avalonia.Android/AvaloniaView.cs | 6 ++-- .../Platform/AndroidScreens.cs | 2 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 36 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index d8df486bb3..f1c3b95aa1 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -35,10 +35,10 @@ namespace Avalonia.Android _root = new EmbeddableControlRoot(_view); _root.Prepare(); - this.SetBackgroundColor(global::Android.Graphics.Color.Transparent); + SetBackgroundColor(global::Android.Graphics.Color.Transparent); OnConfigurationChanged(); - _view.InternalView.SurfaceWindowCreated += InternalView_SurfaceWindowCreated; + _view.InternalView!.SurfaceWindowCreated += InternalView_SurfaceWindowCreated; _view.InternalView.SurfaceWindowDestroyed += InternalView_SurfaceWindowDestroyed; _accessHelper = new AvaloniaAccessHelper(this); @@ -141,7 +141,7 @@ namespace Avalonia.Android { public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView) { - View.FocusChange += ViewImpl_FocusChange; + View!.FocusChange += ViewImpl_FocusChange; } private void ViewImpl_FocusChange(object? sender, FocusChangeEventArgs e) diff --git a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs index 9b0f1bee38..a54a4cbdb8 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidScreens.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidScreens.cs @@ -125,7 +125,7 @@ internal sealed class AndroidScreens : ScreensBase, IDis protected override Screen? ScreenFromTopLevelCore(ITopLevelImpl topLevel) { - var display = ((TopLevelImpl)topLevel).View.Display; + var display = ((TopLevelImpl)topLevel).View?.Display; return display is not null && TryGetScreen(display, out var screen) ? screen : null; } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index da128cec96..eecb2ecc24 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -19,8 +19,8 @@ using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.OpenGL.Egl; using Avalonia.Platform; -using Avalonia.Platform.Surfaces; using Avalonia.Platform.Storage; +using Avalonia.Platform.Surfaces; using Avalonia.Rendering.Composition; using Java.Lang; using ClipboardManager = Android.Content.ClipboardManager; @@ -39,7 +39,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly Clipboard _clipboard; private readonly AndroidLauncher? _launcher; private readonly AndroidScreens? _screens; - private SurfaceViewImpl _view; + private SurfaceViewImpl? _view; private WindowTransparencyLevel _transparencyLevel; public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false) @@ -78,8 +78,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IInputRoot? InputRoot { get; private set; } - public Size ClientSize => _view.Size.ToSize(RenderScaling); - public double RenderScaling => _view.Scaling; + public Size ClientSize => _view?.Size.ToSize(RenderScaling) ?? default; + public double RenderScaling => _view?.Scaling ?? 1; public Action? Closed { get; set; } @@ -91,9 +91,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action? ScalingChanged { get; set; } - public View View => _view; + public View? View => _view; - internal InvalidationAwareSurfaceView InternalView => _view; + internal InvalidationAwareSurfaceView? InternalView => _view; public double DesktopScaling => RenderScaling; public IPlatformHandle Handle { get; } @@ -126,8 +126,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public virtual void Dispose() { _systemNavigationManager.Dispose(); - _view.Dispose(); - _view = null!; + _view?.Dispose(); + _view = null; } protected void OnResized(Size size) @@ -193,7 +193,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform // ReSharper disable once CompareOfFloatsByEqualityOperator if (newScaling != _oldScaling) { - _oldScaling = newScaling; + _oldScaling = newScaling; _tl.ScalingChanged?.Invoke(newScaling); } } @@ -232,7 +232,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { - if(_insetsManager != null) + if (_insetsManager != null) { _insetsManager.SystemBarTheme = themeVariant switch { @@ -245,12 +245,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform AppCompatDelegate.DefaultNightMode = themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes; } - public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new(1, 1, 1); - IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => (_view as IPlatformHandle)?.Handle ?? default; bool EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfoWithWaitPolicy.SkipWaits => true; - PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size => _view.Size; - double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => _view.Scaling; + PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size => _view?.Size ?? default; + double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => _view?.Scaling ?? default; internal AndroidKeyboardEventsHelper KeyboardHelper => _keyboardHelper; @@ -258,7 +258,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - if (_view.Context is not AvaloniaActivity activity) + if (_view?.Context is not AvaloniaActivity activity) return; foreach (var level in transparencyLevels) @@ -341,7 +341,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform return _insetsManager; } - if(featureType == typeof(IClipboard)) + if (featureType == typeof(IClipboard)) { return _clipboard; } @@ -355,7 +355,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { return _screens; } - + return null; } @@ -386,7 +386,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform internal void TextInput(string text) { - if(Input != null) + if (Input != null) { var args = new RawTextInputEventArgs(AndroidKeyboardDevice.Instance!, (ulong)SystemClock.UptimeMillis(), InputRoot!, text); From c0a41d8812f862fec1dc5a6bb700748e1076d959 Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Mon, 27 Apr 2026 13:25:58 +0200 Subject: [PATCH 75/86] Fix headless cleanup race: dispose before signalling TCS (#20664) (#21223) * Fix headless cleanup race: dispose before signalling TCS (#20664) Restructure DispatchCore so application.Dispose() runs in a finally block before tcs.TrySet* is called. Previously, the using-var scope closed after TrySetResult, leaving s_uiThread set when the next non-headless test started. Adds a regression test in IsolationTests that verifies Dispatcher.UIThread is accessible from the calling thread immediately after a headless dispatch completes (PerTest isolation only). * Use Assert.SkipWhen in Dispatch_Cleanup_Should_Complete_Before_Task_Returns * Restrict Dispatch_Cleanup_Should_Complete_Before_Task_Returns to NUnit xUnit's AvaloniaFactDiscoverer wraps every [Fact] in session.Dispatch, so awaiting another session.Dispatch inside the test body deadlocks on the session's single background thread. * Remove new test It isn't reliable enough during CI. --------- Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com> --- .../HeadlessUnitTestSession.cs | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs index cda9c4ae75..0d48dd6919 100644 --- a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs +++ b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs @@ -96,11 +96,16 @@ public sealed class HeadlessUnitTestSession : IDisposable, IAsyncDisposable using var globalCts = token.Register(s => ((CancellationTokenSource)s!).Cancel(), cts, true); using var localCts = cancellationToken.Register(s => ((CancellationTokenSource)s!).Cancel(), cts, true); + var application = _isolated + ? EnsureIsolatedApplication() + : EnsureSharedApplication(); + + bool shouldCancel = false; + Exception? caught = null; + TResult result = default!; + try { - using var application = _isolated - ? EnsureIsolatedApplication() - : EnsureSharedApplication(); var task = action(); if (task.Status != TaskStatus.RanToCompletion) { @@ -110,22 +115,36 @@ public sealed class HeadlessUnitTestSession : IDisposable, IAsyncDisposable if (cts.IsCancellationRequested) { - tcs.TrySetCanceled(cts.Token); - return; + shouldCancel = true; + } + else + { + var frame = new DispatcherFrame(); + using var innerCts = cts.Token.Register(() => frame.Continue = false, true); + Dispatcher.UIThread.PushFrame(frame); + result = task.GetAwaiter().GetResult(); } - - var frame = new DispatcherFrame(); - using var innerCts = cts.Token.Register(() => frame.Continue = false, true); - Dispatcher.UIThread.PushFrame(frame); } - - var result = task.GetAwaiter().GetResult(); - tcs.TrySetResult(result); + else + { + result = task.GetAwaiter().GetResult(); + } } catch (Exception ex) { - tcs.TrySetException(ex); + caught = ex; } + finally + { + application.Dispose(); + } + + if (caught != null) + tcs.TrySetException(caught); + else if (shouldCancel) + tcs.TrySetCanceled(cts.Token); + else + tcs.TrySetResult(result); }, executionContext)); return tcs.Task; } From 5e2f4ccd4942c425d7043f2974332b1d5a99c302 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 27 Apr 2026 13:55:59 +0200 Subject: [PATCH 76/86] Implement GetTemplateDescendants (#21245) * Implement GetTemplateDescendants * Use GetTemplateDescendants in Avalonia.Controls * Use GetTemplateDescendants in tests * Fix obsolete warnings --- .../Primitives/LightDismissOverlayLayer.cs | 2 +- .../Primitives/TemplatedControl.cs | 8 ++-- .../Templates/TemplateExtensions.cs | 37 +++++++++++++++---- .../Layout/FullLayoutTests.cs | 4 +- .../AutoCompleteBoxTests.cs | 2 +- .../CalendarDatePickerTests.cs | 2 +- .../ComboBoxTests.cs | 2 +- .../CommandBarTests.cs | 2 +- .../ContentControlTests.cs | 2 +- .../NumericUpDownTests.cs | 4 +- .../Primitives/PopupTests.cs | 6 +-- .../Primitives/ScrollBarTests.cs | 10 ++--- .../Primitives/TemplatedControlTests.cs | 8 ++-- .../ScrollViewerTests.cs | 4 +- .../TabControlTests.cs | 6 +-- .../Templates/TemplateExtensionsTests.cs | 2 +- .../TransitioningContentControlTests.cs | 2 +- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- .../Data/BindingTests_TemplatedParent.cs | 2 +- .../CompiledBindingExtensionTests.cs | 8 ++-- 20 files changed, 68 insertions(+), 47 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 2e4a1a4828..4662d2532e 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls.Primitives if (visual is TopLevel topLevel) { - manager = topLevel.GetTemplateChildren() + manager = topLevel.GetTemplateDescendants() .OfType() .FirstOrDefault(); } diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index d8ecfa99e8..80f4aa572e 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -317,7 +317,7 @@ namespace Avalonia.Controls.Primitives { if (VisualChildren.Count > 0) { - foreach (var child in this.GetTemplateChildren()) + foreach (var child in this.GetTemplateDescendants()) { child.TemplatedParent = null; ((ISetLogicalParent)child).SetParent(null); @@ -350,11 +350,11 @@ namespace Avalonia.Controls.Primitives /// protected override Control GetTemplateFocusTarget() { - foreach (Control child in this.GetTemplateChildren()) + foreach (var child in this.GetTemplateDescendants()) { - if (GetIsTemplateFocusTarget(child)) + if (child is Control control && GetIsTemplateFocusTarget(control)) { - return child; + return control; } } diff --git a/src/Avalonia.Controls/Templates/TemplateExtensions.cs b/src/Avalonia.Controls/Templates/TemplateExtensions.cs index b8d90ae458..6d10244ba4 100644 --- a/src/Avalonia.Controls/Templates/TemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/TemplateExtensions.cs @@ -1,26 +1,47 @@ using System; using System.Collections.Generic; -using System.Linq; -using Avalonia.Controls; using Avalonia.Controls.Primitives; -using Avalonia.Styling; using Avalonia.VisualTree; namespace Avalonia.Controls.Templates { + /// + /// Contains extension methods for . + /// public static class TemplateExtensions { + /// + /// Gets the list of all control descendants that are part of the template of a , + /// i.e. their is . + /// + /// The control whose descendants will be returned. + /// An enumeration of objects. + [Obsolete($"Use {nameof(GetTemplateDescendants)}")] public static IEnumerable GetTemplateChildren(this TemplatedControl control) { - foreach (Control child in GetTemplateChildren(control, control)) + foreach (var child in control.GetTemplateDescendants()) { - yield return child; + if (child is Control childControl) + { + yield return childControl; + } } } - private static IEnumerable GetTemplateChildren(Control control, TemplatedControl templatedParent) + /// + /// Gets the list of all visual descendants that are part of the template of a , + /// i.e. their is . + /// + /// The control whose descendants will be returned. + /// An enumeration of objects. + public static IEnumerable GetTemplateDescendants(this TemplatedControl control) + { + return GetTemplateDescendants(control, control); + } + + private static IEnumerable GetTemplateDescendants(Visual control, TemplatedControl templatedParent) { - foreach (Control child in control.GetVisualChildren()) + foreach (var child in control.GetVisualChildren()) { var childTemplatedParent = child.TemplatedParent; @@ -31,7 +52,7 @@ namespace Avalonia.Controls.Templates if (childTemplatedParent != null) { - foreach (var descendant in GetTemplateChildren(child, templatedParent)) + foreach (var descendant in GetTemplateDescendants(child, templatedParent)) { yield return descendant; } diff --git a/tests/Avalonia.Base.UnitTests/Layout/FullLayoutTests.cs b/tests/Avalonia.Base.UnitTests/Layout/FullLayoutTests.cs index 4922fbe37b..2daedca80c 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/FullLayoutTests.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/FullLayoutTests.cs @@ -86,8 +86,8 @@ namespace Avalonia.Base.UnitTests.Layout Assert.Equal(new Point(300, 200), Position(scrollViewer)); Assert.Equal(new Size(400, 400), textBlock.Bounds.Size); - var scrollBars = scrollViewer.GetTemplateChildren().OfType().ToList(); - var presenters = scrollViewer.GetTemplateChildren().OfType().ToList(); + var scrollBars = scrollViewer.GetTemplateDescendants().OfType().ToList(); + var presenters = scrollViewer.GetTemplateDescendants().OfType().ToList(); Assert.Equal(2, scrollBars.Count); Assert.Single(presenters); diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 3bdec3bc7e..4232624fad 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -1235,7 +1235,7 @@ namespace Avalonia.Controls.UnitTests } private TextBox GetTextBox(AutoCompleteBox control) { - return control.GetTemplateChildren() + return control.GetTemplateDescendants() .OfType() .First(); } diff --git a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs index d97a9729fb..b620601f71 100644 --- a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs @@ -155,7 +155,7 @@ namespace Avalonia.Controls.UnitTests private TextBox GetTextBox(CalendarDatePicker control) { - return control.GetTemplateChildren() + return control.GetTemplateDescendants() .OfType() .First(); } diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 578484557f..b3212ed0f7 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -834,7 +834,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Presenter!.ApplyTemplate(); - var containerPanel = target.GetTemplateChildren().OfType().FirstOrDefault(x => x.Name == "container"); + var containerPanel = target.GetTemplateDescendants().OfType().FirstOrDefault(x => x.Name == "container"); var editableTextBox = containerPanel?.GetVisualDescendants().OfType().FirstOrDefault(x => x.Name == "PART_EditableTextBox"); var popup = containerPanel?.GetVisualDescendants().OfType().FirstOrDefault(x => x.Name == "PART_Popup"); var popupScrollViewer = popup?.Child as ScrollViewer; diff --git a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs index e368d93892..a7996a46ca 100644 --- a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs @@ -495,7 +495,7 @@ public class CommandBarDefaultsTests : ScopedTestBase command.ApplyStyling(); command.ApplyTemplate(); - var presenter = command.GetTemplateChildren() + var presenter = command.GetTemplateDescendants() .OfType() .Single(x => x.Name == "PART_IconPresenter"); diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index 30d789f08f..87eb4ef2af 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -57,7 +57,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Presenter!.ApplyTemplate(); - foreach (Control child in target.GetTemplateChildren()) + foreach (var child in target.GetTemplateDescendants().OfType()) Assert.Equal("foo", child.Tag); } diff --git a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs index 868b33d7bd..b1a402435d 100644 --- a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs +++ b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs @@ -142,7 +142,7 @@ namespace Avalonia.Controls.UnitTests } private static TextBox GetTextBox(NumericUpDown control) { - return control.GetTemplateChildren() + return control.GetTemplateDescendants() .OfType() .Select(b => b.Content) .OfType() @@ -151,7 +151,7 @@ namespace Avalonia.Controls.UnitTests private static ButtonSpinner GetSpinner(NumericUpDown control) { - return control.GetTemplateChildren() + return control.GetTemplateDescendants() .OfType() .First(); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index ca38a56166..dcf9b6f653 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -329,7 +329,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); + var popup = (Popup)target.GetTemplateDescendants().First(x => x.Name == "popup"); popup.Open(); var popupRoot = (Control)popup.Host!; @@ -420,7 +420,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); + var popup = (Popup)target.GetTemplateDescendants().First(x => x.Name == "popup"); popup.Open(); var popupRoot = (Control)popup.Host!; @@ -515,7 +515,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); + var popup = (Popup)target.GetTemplateDescendants().First(x => x.Name == "popup"); popup.Open(); var popupRoot = (Control)popup.Host!; diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs index 215cdb64b3..b6bfab09cc 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs @@ -20,7 +20,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; target.ApplyTemplate(); - var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track"); + var track = (Track)target.GetTemplateDescendants().First(x => x.Name == "track"); target.Value = 50; Assert.Equal(50, track.Value); @@ -35,7 +35,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; target.ApplyTemplate(); - var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track"); + var track = (Track)target.GetTemplateDescendants().First(x => x.Name == "track"); track.Value = 50; Assert.Equal(50, target.Value); @@ -51,7 +51,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track"); + var track = (Track)target.GetTemplateDescendants().First(x => x.Name == "track"); target.Value = 25; track.Value = 50; @@ -68,7 +68,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track"); + var track = (Track)target.GetTemplateDescendants().First(x => x.Name == "track"); var raisedEvent = Assert.Raises( handler => target.Scroll += handler, @@ -97,7 +97,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track"); + var track = (Track)target.GetTemplateDescendants().First(x => x.Name == "track"); var raisedEvent = Assert.Raises( handler => target.Scroll += handler, diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 721ad45c11..8a35a75dc4 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -204,7 +204,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - foreach (Control child in target.GetTemplateChildren()) + foreach (var child in target.GetTemplateDescendants().OfType()) Assert.Equal("foo", child.Tag); } @@ -237,11 +237,11 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); - var contentControl = target.GetTemplateChildren().OfType().Single(); + var contentControl = target.GetTemplateDescendants().OfType().Single(); contentControl.ApplyTemplate(); - var border = contentControl.GetTemplateChildren().OfType().Single(); - var presenter = contentControl.GetTemplateChildren().OfType().Single(); + var border = contentControl.GetTemplateDescendants().OfType().Single(); + var presenter = contentControl.GetTemplateDescendants().OfType().Single(); var decorator = (Decorator)presenter.Content!; var textBlock = (TextBlock)decorator.Child!; diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index e71f4bd8b7..ec1cd5fb66 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs @@ -610,9 +610,9 @@ namespace Avalonia.Controls.UnitTests private Thumb GetVerticalThumb(ScrollViewer target) { var scrollbar = Assert.IsType( - target.GetTemplateChildren().FirstOrDefault(x => x.Name == "PART_VerticalScrollBar")); + target.GetTemplateDescendants().FirstOrDefault(x => x.Name == "PART_VerticalScrollBar")); var track = Assert.IsType( - scrollbar.GetTemplateChildren().FirstOrDefault(x => x.Name == "track")); + scrollbar.GetTemplateDescendants().FirstOrDefault(x => x.Name == "track")); return Assert.IsType(track.Thumb); } diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 06a7054766..ea14eb5b2f 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -1701,7 +1701,7 @@ namespace Avalonia.Controls.UnitTests tabItem.ApplyTemplate(); tabItem.Presenter!.UpdateChild(); - var iconPresenter = tabItem.GetTemplateChildren().OfType().First(x => x.Name == "PART_IconPresenter"); + var iconPresenter = tabItem.GetTemplateDescendants().OfType().First(x => x.Name == "PART_IconPresenter"); Assert.NotNull(iconPresenter); Assert.Equal("home", iconPresenter!.Content); Assert.NotNull(iconPresenter.ContentTemplate); @@ -1729,7 +1729,7 @@ namespace Avalonia.Controls.UnitTests tabItem.ApplyTemplate(); tabItem.Presenter!.UpdateChild(); - var iconPresenter = tabItem.GetTemplateChildren().OfType().First(x => x.Name == "PART_IconPresenter"); + var iconPresenter = tabItem.GetTemplateDescendants().OfType().First(x => x.Name == "PART_IconPresenter"); Assert.NotNull(iconPresenter); Assert.Same(icon, iconPresenter!.Content); Assert.Null(iconPresenter.ContentTemplate); @@ -1748,7 +1748,7 @@ namespace Avalonia.Controls.UnitTests tabItem.ApplyTemplate(); tabItem.Presenter!.UpdateChild(); - var iconPresenter = tabItem.GetTemplateChildren().OfType().First(x => x.Name == "PART_IconPresenter"); + var iconPresenter = tabItem.GetTemplateDescendants().OfType().First(x => x.Name == "PART_IconPresenter"); Assert.Equal("first", iconPresenter!.Content); tabItem.Icon = "second"; diff --git a/tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs b/tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs index d540ae9444..734c2a916d 100644 --- a/tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs @@ -38,7 +38,7 @@ namespace Avalonia.Controls.Templates.UnitTests border3.Child = border4; border4.Child = border5; - var result = target.GetTemplateChildren().Select(x => x.Name).ToArray(); + var result = target.GetTemplateDescendants().Select(x => x.Name).ToArray(); Assert.Equal(new[] { "border1", "inner", "border4" }, result); } diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index b84fef1fb2..7f42e20307 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -371,7 +371,7 @@ namespace Avalonia.Controls.UnitTests private static ContentPresenter GetContentPresenters2(TransitioningContentControl target) { return Assert.IsType(target - .GetTemplateChildren() + .GetTemplateDescendants() .First(x => x.Name == "PART_ContentPresenter2")); } diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 33edc0d00d..108ba2c5f4 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -251,7 +251,7 @@ namespace Avalonia.LeakTests Assert.Same(textBox, window.Presenter!.Child); // Get the border from the TextBox template. - var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border"); + var border = textBox.GetTemplateDescendants().FirstOrDefault(x => x.Name == "border"); // The TextBox should have subscriptions to its Classes collection from the // default theme. diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs index b6cd86c563..1f2dcff0b7 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs @@ -67,7 +67,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data window.ApplyTemplate(); button.ApplyTemplate(); - Assert.Equal(button.Tag, button.GetTemplateChildren().OfType().First().ColumnDefinitions[0].Width); + Assert.Equal(button.Tag, button.GetTemplateDescendants().OfType().First().ColumnDefinitions[0].Width); } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 727b3ddc45..99a79ded75 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1011,7 +1011,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions contentControl.DataContext = new TestDataContext(); // should be ignored contentControl.Measure(new Size(10, 10)); - var result = contentControl.GetTemplateChildren().OfType().First(); + var result = contentControl.GetTemplateDescendants().OfType().First(); Assert.Equal(false, result.Focusable); } } @@ -1046,7 +1046,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions textBox.DataContext = new TestDataContext(); // should be ignored textBox.Measure(new Size(10, 10)); - var result = textBox.GetTemplateChildren().OfType().First(); + var result = textBox.GetTemplateDescendants().OfType().First(); Assert.Equal(textBox.InnerLeftContent, result.Content); } } @@ -1080,7 +1080,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions window.ApplyTemplate(); button.ApplyTemplate(); - Assert.Equal(button.Tag, button.GetTemplateChildren().OfType().First().ColumnDefinitions[0].Width); + Assert.Equal(button.Tag, button.GetTemplateDescendants().OfType().First().ColumnDefinitions[0].Width); } } @@ -1109,7 +1109,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var contentControl = AvaloniaRuntimeXamlLoader.Parse(xaml); contentControl.Measure(new Size(10, 10)); - var result = contentControl.GetTemplateChildren().OfType().First(); + var result = contentControl.GetTemplateDescendants().OfType().First(); Assert.Equal("Hello", result.Content); } From 28d2c288d5baab4accf3209efe2a810e5ca57ece Mon Sep 17 00:00:00 2001 From: Zhang Dian <54255897+zdpcdt@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:54:35 +0800 Subject: [PATCH 77/86] feat: add HeaderTemplate to DrawerPage header. (#21275) --- .../Controls/DrawerPage.xaml | 34 ++++++++++--------- .../Controls/DrawerPage.xaml | 34 ++++++++++--------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/DrawerPage.xaml b/src/Avalonia.Themes.Fluent/Controls/DrawerPage.xaml index 612db8d69c..e93d6c9022 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DrawerPage.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DrawerPage.xaml @@ -112,14 +112,15 @@ VerticalAlignment="Center"/> - + - + - + - + Date: Tue, 28 Apr 2026 18:04:49 +0800 Subject: [PATCH 78/86] Update SkiaSharp package versions to support Bionic and fix LSX error (#21263) * Update HarfBuzzSharp and SkiaSharp package versions https://github.com/mono/SkiaSharp/releases/tag/v3.119.4-preview.1.1 SkiaSharp v3.119.4-preview.1.1 has many features: Add SkiaSharp.Views.Gtk4 using GirCore.Gtk-4.0 bindings Add Linux Bionic native assets support, that can make high-performance Android application Add PolySharp for C# 13 support on legacy TFMs Add Tizen x64 and arm64 native build support Delay-load D3D12 DLLs to prevent crashes on systems without DX12 Bump Skia milestone from 119 to 133 * Restore HarfBuzzSharp version --- Directory.Packages.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7d2780e29e..942a01d13b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -48,9 +48,9 @@ - - - + + + From ea94d81609799a2af7189033ddd9da80fcd059fb Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 28 Apr 2026 03:17:02 -0700 Subject: [PATCH 79/86] Throw ObjectDisposedException when PlatformImpl is null in HeadlessExtension (#21276) --- .../Avalonia.Headless/HeadlessWindowExtensions.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs index 78b89d6cb1..8bb3bf4f34 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs @@ -153,7 +153,11 @@ public static class HeadlessWindowExtensions private static IHeadlessWindow GetImpl(this TopLevel topLevel) { - return topLevel.PlatformImpl as IHeadlessWindow ?? - throw new InvalidOperationException("TopLevel must be a headless window."); + return topLevel.PlatformImpl switch + { + null => throw new ObjectDisposedException(topLevel.GetType().Name), + IHeadlessWindow headless => headless, + _ => throw new InvalidOperationException("TopLevel must be a headless window.") + }; } } From 171d932b43282edeffd1255b028454bbafb72846 Mon Sep 17 00:00:00 2001 From: Shirasagi Date: Tue, 28 Apr 2026 19:57:38 +0800 Subject: [PATCH 80/86] Suppress ExecutionContext flow for render scheduling (#21273) * Add regression tests * fix executioncontext leakage * change DispatcherOperation ctor parameter to accept bool captureExecutionContext --- src/Avalonia.Base/Media/MediaContext.cs | 6 +- .../Threading/DispatcherOperation.cs | 10 ++- .../DispatcherTests.cs | 84 +++++++++++++++++++ 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs index d82144688d..943bf54149 100644 --- a/src/Avalonia.Base/Media/MediaContext.cs +++ b/src/Avalonia.Base/Media/MediaContext.cs @@ -91,7 +91,9 @@ internal partial class MediaContext : ICompositorScheduler if (_inputMarkerOp == null) { - _inputMarkerOp = _dispatcher.InvokeAsync(_inputMarkerHandler, DispatcherPriority.Input); + _inputMarkerOp = new DispatcherOperation(_dispatcher, DispatcherPriority.Input, _inputMarkerHandler, + throwOnUiThread: true, captureExecutionContext: false); + _dispatcher.InvokeAsyncImpl(_inputMarkerOp, CancellationToken.None); _inputMarkerAddedAt = _time.Elapsed; } else if (!now && (_time.Elapsed - _inputMarkerAddedAt).TotalSeconds > MaxSecondsWithoutInput) @@ -99,7 +101,7 @@ internal partial class MediaContext : ICompositorScheduler priority = DispatcherPriority.Input; } - var renderOp = new DispatcherOperation(_dispatcher, priority, _render, throwOnUiThread: true); + var renderOp = new DispatcherOperation(_dispatcher, priority, _render, throwOnUiThread: true, captureExecutionContext: false); _nextRenderOp = renderOp; _dispatcher.InvokeAsyncImpl(renderOp, CancellationToken.None); } diff --git a/src/Avalonia.Base/Threading/DispatcherOperation.cs b/src/Avalonia.Base/Threading/DispatcherOperation.cs index ea48fa31b0..1e19ccd196 100644 --- a/src/Avalonia.Base/Threading/DispatcherOperation.cs +++ b/src/Avalonia.Base/Threading/DispatcherOperation.cs @@ -44,18 +44,20 @@ public class DispatcherOperation private DispatcherPriority _priority; private readonly ExecutionContext? _executionContext; - internal DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, Action callback, bool throwOnUiThread) : - this(dispatcher, priority, throwOnUiThread) + internal DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, Action callback, bool throwOnUiThread, + bool captureExecutionContext = true) : + this(dispatcher, priority, throwOnUiThread, captureExecutionContext) { Callback = callback; } - private protected DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, bool throwOnUiThread) + private protected DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, bool throwOnUiThread, + bool captureExecutionContext = true) { ThrowOnUiThread = throwOnUiThread; Priority = priority; Dispatcher = dispatcher; - _executionContext = ExecutionContext.Capture(); + _executionContext = captureExecutionContext ? ExecutionContext.Capture() : null; } internal string DebugDisplay diff --git a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs index ffb94cc5d6..2efb648f4d 100644 --- a/tests/Avalonia.Base.UnitTests/DispatcherTests.cs +++ b/tests/Avalonia.Base.UnitTests/DispatcherTests.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Threading; using Avalonia.UnitTests; @@ -600,6 +601,19 @@ public partial class DispatcherTests public AsyncLocal AsyncLocalField { get; set; } = new AsyncLocal(); } + private sealed class AsyncLocalMeasureControl(Func getValue, Action setValue) : Control + { + public bool RecordMeasure { get; set; } + + protected override Size MeasureOverride(Size availableSize) + { + if (RecordMeasure) + setValue(getValue()); + + return new Size(1, 1); + } + } + [Fact] public async Task ExecutionContextIsPreservedInDispatcherInvokeAsync() { @@ -697,6 +711,76 @@ public partial class DispatcherTests Assert.Null(test); } + [Fact] + public void MediaContextRenderSchedulingDoesNotCaptureAmbientExecutionContext() + { + var impl = new SimpleDispatcherWithBackgroundProcessingImpl(); + using var services = new DispatcherServices(impl); + Dispatcher.InitializeUIThreadDispatcher(impl); + + var testObject = new AsyncLocalTestClass(); + string? test = "Not measured"; + var control = new AsyncLocalMeasureControl(() => testObject.AsyncLocalField.Value, value => test = value); + var root = new TestRoot { Child = control }; + + root.ExecuteInitialLayoutPass(); + control.RecordMeasure = true; + + Dispatcher.UIThread.Post(() => + { + testObject.AsyncLocalField.Value = "Initial Value"; + control.InvalidateMeasure(); + testObject.AsyncLocalField.Value = null; + }); + + Assert.True(impl.AskedForSignal); + impl.ExecuteSignal(); + + Assert.Null(test); + } + + [Fact] + public void MediaContextRenderSchedulingAllowsAlreadySuppressedExecutionContextFlow() + { + var impl = new SimpleDispatcherWithBackgroundProcessingImpl(); + using var services = new DispatcherServices(impl); + Dispatcher.InitializeUIThreadDispatcher(impl); + + var testObject = new AsyncLocalTestClass(); + string? test = "Not measured"; + Exception? schedulingException = null; + var control = new AsyncLocalMeasureControl(() => testObject.AsyncLocalField.Value, value => test = value); + var root = new TestRoot { Child = control }; + + root.ExecuteInitialLayoutPass(); + control.RecordMeasure = true; + + Dispatcher.UIThread.Post(() => + { + testObject.AsyncLocalField.Value = "Initial Value"; + + try + { + using (ExecutionContext.SuppressFlow()) + { + control.InvalidateMeasure(); + } + } + catch (Exception e) + { + schedulingException = e; + } + + testObject.AsyncLocalField.Value = null; + }); + + Assert.True(impl.AskedForSignal); + impl.ExecuteSignal(); + + Assert.Null(schedulingException); + Assert.Null(test); + } + [Fact] public async Task ExecutionContextCultureInfoIsPreservedInDispatcherInvokeAsync() { From 4785a10120cbf85e54a74729116081f374b44fa2 Mon Sep 17 00:00:00 2001 From: Rowe Wilson Frederisk Holme Date: Thu, 30 Apr 2026 00:54:40 +0800 Subject: [PATCH 81/86] Fix typo (#21281) --- .../webapp/modules/storage/storageItem.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts index 2d30d1901b..0e76b93646 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts @@ -52,7 +52,7 @@ export class StorageItem { throw new Error("StorageItem is not a file"); } - await item.verityPermissions("read"); + await item.verifyPermissions("read"); const file = await (item.handle as FileSystemFileHandle).getFile(); return file; @@ -63,7 +63,7 @@ export class StorageItem { throw new Error("StorageItem is not a writeable file"); } - await item.verityPermissions("readwrite"); + await item.verifyPermissions("readwrite"); return await (item.handle as FileSystemFileHandle).createWritable({ keepExistingData: false }); } @@ -102,7 +102,7 @@ export class StorageItem { throw new TypeError("Unable to create item in the requested directory"); } - await item.verityPermissions("readwrite"); + await item.verifyPermissions("readwrite"); // The file should be truncated when it is created. const fileHandle = await ((item.handle as any).getFileHandle(name, { create: true }) as Promise); const writable = await fileHandle.createWritable({ keepExistingData: false }); @@ -115,7 +115,7 @@ export class StorageItem { return null; } - await item.verityPermissions("read"); + await item.verifyPermissions("read"); return await ((item.handle as any).getFileHandle(name) as Promise); } @@ -125,7 +125,7 @@ export class StorageItem { throw new TypeError("Unable to create item in the requested directory"); } - await item.verityPermissions("readwrite"); + await item.verifyPermissions("readwrite"); return await ((item.handle as any).getDirectoryHandle(name, { create: true }) as Promise); } @@ -135,7 +135,7 @@ export class StorageItem { return null; } - await item.verityPermissions("read"); + await item.verifyPermissions("read"); return await ((item.handle as any).getDirectoryHandle(name) as Promise); } @@ -145,7 +145,7 @@ export class StorageItem { return null; } - await item.verityPermissions("readwrite"); + await item.verifyPermissions("readwrite"); return await ((item.handle as any).remove({ recursive: true }) as Promise); } @@ -158,12 +158,12 @@ export class StorageItem { throw new TypeError("Unable to move item to the requested directory"); } - await item.verityPermissions("readwrite"); + await item.verifyPermissions("readwrite"); return await ((item.handle as any).move(destination /*, newName */) as Promise); } - private async verityPermissions(mode: "read" | "readwrite"): Promise { + private async verifyPermissions(mode: "read" | "readwrite"): Promise { if (!this.handle) { return; } From 8c3ddb105f3ac1c6129b393055fd917d944f7687 Mon Sep 17 00:00:00 2001 From: Matt <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:16:49 +0300 Subject: [PATCH 82/86] Win32: Run file picker on dedicated STA thread to avoid MTA deadlock (#21266) * Run Win32 file picker on a dedicated thread to avoid MTA deadlock * Add threading comment in Win32StorageProvider --------- Co-authored-by: Julien Lebosquain --- .../Avalonia.Win32/Win32StorageProvider.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs index a61af0de97..6519933de7 100644 --- a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs +++ b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Avalonia.Controls.Utils; using Avalonia.Platform.Storage; @@ -96,7 +97,13 @@ namespace Avalonia.Win32 Func convert) where TStorageItem : IStorageItem { - return Task.Factory.StartNew(() => + // TODO13: verify that we're on the correct dispatcher, matching other platforms' implementations. + // We should then be able to remove the dedicated thread and simply use IFileDialog directly (needs to be reconfirmed). + + var tcs = new TaskCompletionSource<(IReadOnlyList, int)>( + TaskCreationOptions.RunContinuationsAsynchronously); + + var thread = new Thread(() => { IReadOnlyList result = []; try @@ -179,7 +186,8 @@ namespace Avalonia.Win32 if ((uint)showResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) { - return (result, typeIndex); + tcs.SetResult((result, typeIndex)); + return; } else if ((uint)showResult != (uint)UnmanagedMethods.HRESULT.S_OK) { @@ -210,14 +218,23 @@ namespace Avalonia.Win32 result = [convert(singleResult)]; } - return (result, typeIndex); + tcs.SetResult((result, typeIndex)); } catch (COMException ex) { var message = new Win32Exception(ex.HResult).Message; - throw new COMException(message, ex); + tcs.SetException(new COMException(message, ex)); } - }, TaskCreationOptions.LongRunning); + catch (Exception ex) + { + tcs.SetException(ex); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.IsBackground = true; + thread.Start(); + + return tcs.Task; } private static string? GetParsingName(IShellItem shellItem) From 0c9713d9a7ae2b2aea576533e076a838ef662340 Mon Sep 17 00:00:00 2001 From: Dong Bin <14807942+rabbitism@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:34:32 +0800 Subject: [PATCH 83/86] fix: update XML documentation to reflect correct type references (#21283) --- .../AutoCompleteBox.Properties.cs | 4 +-- .../AutoCompleteBox/AutoCompleteBox.cs | 20 ++++++------- src/Avalonia.Controls/Calendar/Calendar.cs | 28 +++++++++---------- .../Calendar/CalendarButton.cs | 2 +- .../Calendar/CalendarDateRange.cs | 4 +-- .../Calendar/CalendarItem.cs | 2 +- .../CalendarDatePicker/CalendarDatePicker.cs | 2 +- .../SelectingItemsControlSelectionAdapter.cs | 2 +- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs index 1faa9b340e..634daa767d 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs @@ -273,10 +273,10 @@ namespace Avalonia.Controls } /// - /// Gets or sets the used + /// Gets or sets the used /// to display each item in the drop-down portion of the control. /// - /// The used to + /// The used to /// display each item in the drop-down. The default is null. /// /// You use the ItemTemplate property to specify the visualization diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index f7f5250649..fb5c959c90 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -615,11 +615,11 @@ namespace Avalonia.Controls /// /// Returns the - /// part, if + /// part, if /// possible. /// /// - /// A object, + /// A object, /// if possible. Otherwise, null. /// protected virtual ISelectionAdapter? GetSelectionAdapterPart(INameScope nameScope) @@ -681,7 +681,7 @@ namespace Avalonia.Controls /// /// Provides handling for the - /// event. + /// event. /// /// A /// that contains the event data. @@ -752,9 +752,9 @@ namespace Avalonia.Controls /// /// Provides handling for the - /// event. + /// event. /// - /// A + /// A /// that contains the event data. protected override void OnGotFocus(FocusChangedEventArgs e) { @@ -764,9 +764,9 @@ namespace Avalonia.Controls /// /// Provides handling for the - /// event. + /// event. /// - /// A + /// A /// that contains the event data. protected override void OnLostFocus(FocusChangedEventArgs e) { @@ -974,7 +974,7 @@ namespace Avalonia.Controls /// event. /// /// A - /// + /// /// that contains the event data. protected virtual void OnDropDownOpening(CancelEventArgs e) { @@ -1000,7 +1000,7 @@ namespace Avalonia.Controls /// event. /// /// A - /// + /// /// that contains the event data. protected virtual void OnDropDownClosing(CancelEventArgs e) { @@ -1559,7 +1559,7 @@ namespace Avalonia.Controls /// /// Notifies the /// that the - /// + /// /// property has been set and the data can be filtered to provide /// possible matches in the drop-down. /// diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index dc8ea002d9..819019a634 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -363,7 +363,7 @@ namespace Avalonia.Controls /// /// /// A value indicating what length of time the - /// should display. + /// should display. /// public CalendarMode DisplayMode { @@ -450,7 +450,7 @@ namespace Avalonia.Controls /// /// /// A value that indicates the current selection mode. The default is - /// . + /// . /// /// /// @@ -537,18 +537,18 @@ namespace Avalonia.Controls /// The date currently selected. The default is null. /// /// The given date is outside the range specified by - /// - /// and + /// + /// and /// -or- /// The given date is in the - /// + /// /// collection. /// /// /// If set to anything other than null when - /// is + /// is /// set to - /// . + /// . /// /// /// Use this property when SelectionMode is set to SingleDate. In other @@ -615,7 +615,7 @@ namespace Avalonia.Controls /// Gets a collection of selected dates. /// /// - /// A + /// A /// object that contains the currently selected dates. The default is an /// empty collection. /// @@ -763,9 +763,9 @@ namespace Avalonia.Controls /// The date to display. /// /// The given date is not in the range specified by - /// + /// /// and - /// . + /// . /// /// /// @@ -1446,7 +1446,7 @@ namespace Avalonia.Controls /// /// Occurs when the - /// + /// /// property is changed. /// /// @@ -1456,7 +1456,7 @@ namespace Avalonia.Controls /// /// Occurs when the - /// + /// /// property is changed. /// public event EventHandler? DisplayModeChanged; @@ -2214,7 +2214,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the - /// class. + /// class. /// public Calendar() { @@ -2230,7 +2230,7 @@ namespace Avalonia.Controls /// /// Builds the visual tree for the - /// when a new + /// when a new /// template is applied. /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 6bc704888e..d4cb17a997 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -97,7 +97,7 @@ namespace Avalonia.Controls.Primitives /// /// Builds the visual tree for the - /// + /// /// when a new template is applied. /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) diff --git a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs index 793ef7a2ee..826886efb8 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the - /// class + /// class /// with a single date. /// /// The date to be represented by the range. @@ -35,7 +35,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the - /// class + /// class /// with a range of dates. /// /// diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 3d548e15b9..e33298023f 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -244,7 +244,7 @@ namespace Avalonia.Controls.Primitives /// /// Builds the visual tree for the - /// + /// /// when a new template is applied. /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index 6a3f44aa5f..097fd2fb85 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls public event EventHandler? CalendarOpened; /// - /// Occurs when + /// Occurs when /// is assigned a value that cannot be interpreted as a date. /// public event EventHandler? DateValidationError; diff --git a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs index bdb6dad5d3..127925865c 100644 --- a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs +++ b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs @@ -91,7 +91,7 @@ namespace Avalonia.Controls.Utils /// /// Initializes a new instance of the - /// + /// /// class with the specified /// /// control. From 550b57db3340857f1ddd883f7a71ea95c4faceac Mon Sep 17 00:00:00 2001 From: Diego Rivero <7670773+dirivero@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:04:13 -0300 Subject: [PATCH 84/86] Fix DeviceLuid byte ordering on MacOS GPU interop (#21293) bytes.Reverse() was resolving to System.Linq.Enumberable.Reverse(). Forcing now to MemoryExtensions.Reverse(Span) by calling AsSpan(). --- src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs | 2 +- src/Avalonia.Native/Metal.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs index 9efe33689f..7e111735cf 100644 --- a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs +++ b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs @@ -264,7 +264,7 @@ namespace Avalonia.Native { // We are reversing bytes to match MoltenVK (LUID is a Vulkan term after all) var bytes = BitConverter.GetBytes(registryId); - bytes.Reverse(); + bytes.AsSpan().Reverse(); DeviceLuid = bytes; } } diff --git a/src/Avalonia.Native/Metal.cs b/src/Avalonia.Native/Metal.cs index 3137ce1b6b..fba317cc53 100644 --- a/src/Avalonia.Native/Metal.cs +++ b/src/Avalonia.Native/Metal.cs @@ -102,7 +102,7 @@ internal class MetalExternalObjectsFeature : IMetalExternalObjectsFeature if (_device.GetIOKitRegistryId(®istryId) != 0) { var bytes = BitConverter.GetBytes(registryId); - bytes.Reverse(); + bytes.AsSpan().Reverse(); DeviceLuid = bytes; } } From 1365b64d9158248f071a8c8c43313101590d8979 Mon Sep 17 00:00:00 2001 From: Tom Edwards <109803929+TomEdwardsEnscape@users.noreply.github.com> Date: Mon, 4 May 2026 11:16:43 +0200 Subject: [PATCH 85/86] Avoid exceptions when getting position from a PointerEventArgs (#21301) Return null when an IPresentationSource can't convert from/to screen coordinates Document exceptions in VisualExtensions PointToClient and PointToScreen Add tests for cross-root pointer event positions --- src/Avalonia.Base/Input/PointerEventArgs.cs | 40 ++++++----- .../Rendering/IPresentationSource.cs | 4 +- src/Avalonia.Base/VisualExtensions.cs | 10 +-- .../PresentationSource.RenderRoot.cs | 4 +- .../Input/MouseDeviceTests.cs | 72 ++++++++++++++++++- tests/Avalonia.RenderTests/TestRenderRoot.cs | 4 +- tests/Avalonia.UnitTests/TestRoot.cs | 4 +- 7 files changed, 106 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 9285e65fa5..ee1b54b792 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -3,14 +3,14 @@ using System.Collections.Generic; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.Metadata; -using Avalonia.VisualTree; +using Avalonia.Rendering; namespace Avalonia.Input { public class PointerEventArgs : RoutedEventArgs, IKeyModifiersEventArgs { - private readonly Visual? _rootVisual; - private readonly Point _rootVisualPosition; + private readonly IPresentationSource? _eventPresentationSource; // the original observer of the event + private readonly Point _presentationSourcePosition; private readonly PointerPointProperties _properties; private readonly Lazy?>? _previousPoints; @@ -24,8 +24,8 @@ namespace Avalonia.Input : base(routedEvent) { Source = source; - _rootVisual = rootVisual; - _rootVisualPosition = rootVisualPosition; + _eventPresentationSource = rootVisual?.PresentationSource; + _presentationSourcePosition = rootVisualPosition; _properties = properties; Pointer = pointer; Timestamp = timestamp; @@ -76,23 +76,25 @@ namespace Avalonia.Input private Point GetPosition(Point pt, Visual? relativeTo) { - if (_rootVisual == null) - return default; if (relativeTo == null) return pt; + if (_eventPresentationSource == null || relativeTo?.PresentationSource?.RootVisual == null) + return default; - // If the visual the user passed in, is not connected to the same visual root - // (i.e. they called it for a control inside a popup. - if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot) && relativeTo.VisualRoot is { }) + if (relativeTo.PresentationSource != _eventPresentationSource) { - // Convert to absolute screen coordinates. - var screenPt = _rootVisual.PointToScreen(pt); - - // Convert to client co-ordinates of the visual inside the other visual root. - return relativeTo.PointToClient(screenPt); + if (_eventPresentationSource.PointToScreen(pt) is { } screenPt && + relativeTo.PresentationSource.PointToClient(screenPt) is { } targetClientPt) + { + pt = targetClientPt; + } + else + { + return default; + } } - return pt * _rootVisual.TransformToVisual(relativeTo) ?? default; + return relativeTo.PresentationSource.RootVisual.TranslatePoint(pt, relativeTo) ?? default; } /// @@ -100,7 +102,7 @@ namespace Avalonia.Input /// /// The visual whose coordinate system to use. Pass null for toplevel coordinate system /// The pointer position in the control's coordinates. - public Point GetPosition(Visual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); + public Point GetPosition(Visual? relativeTo) => GetPosition(_presentationSourcePosition, relativeTo); /// /// Returns the PointerPoint associated with the current event @@ -117,7 +119,7 @@ namespace Avalonia.Input /// public IReadOnlyList GetIntermediatePoints(Visual? relativeTo) { - var previousPoints = _previousPoints?.Value; + var previousPoints = _previousPoints?.Value; if (previousPoints == null || previousPoints.Count == 0) return new[] { GetCurrentPoint(relativeTo) }; var points = new PointerPoint[previousPoints.Count + 1]; @@ -145,7 +147,7 @@ namespace Avalonia.Input /// public PointerPointProperties Properties => _properties; } - + public enum MouseButton { None, diff --git a/src/Avalonia.Base/Rendering/IPresentationSource.cs b/src/Avalonia.Base/Rendering/IPresentationSource.cs index bf5beebe49..ff9da11afa 100644 --- a/src/Avalonia.Base/Rendering/IPresentationSource.cs +++ b/src/Avalonia.Base/Rendering/IPresentationSource.cs @@ -41,6 +41,6 @@ public interface IPresentationSource /// internal Size ClientSize { get; } - internal PixelPoint PointToScreen(Point point); - internal Point PointToClient(PixelPoint point); + internal PixelPoint? PointToScreen(Point point); + internal Point? PointToClient(PixelPoint point); } diff --git a/src/Avalonia.Base/VisualExtensions.cs b/src/Avalonia.Base/VisualExtensions.cs index 3df2eca039..fa4236bf46 100644 --- a/src/Avalonia.Base/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualExtensions.cs @@ -14,12 +14,13 @@ namespace Avalonia /// The visual. /// The point in screen coordinates. /// The point in client coordinates. + /// Thown when does not belong to a visual tree. public static Point PointToClient(this Visual visual, PixelPoint point) { var source = visual.PresentationSource; var root = source?.RootVisual ?? - throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual)); - var rootPoint = source.PointToClient(point); + throw new ArgumentException("Visual does not belong to a visual tree.", nameof(visual)); + var rootPoint = source.PointToClient(point) ?? default; return root.TranslatePoint(rootPoint, visual)!.Value; } @@ -29,13 +30,14 @@ namespace Avalonia /// The visual. /// The point in client coordinates. /// The point in screen coordinates. + /// Thown when does not belong to a visual tree. public static PixelPoint PointToScreen(this Visual visual, Point point) { var source = visual.PresentationSource; var root = source?.RootVisual ?? - throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual)); + throw new ArgumentException("Visual does not belong to a visual tree.", nameof(visual)); var p = visual.TranslatePoint(point, root); - return source.PointToScreen(p!.Value); + return source.PointToScreen(p!.Value) ?? default; } /// diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs index 2a9e833a48..e5fb12eba4 100644 --- a/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs +++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs @@ -23,9 +23,9 @@ internal partial class PresentationSource _pointerOverPreProcessor?.SceneInvalidated(sceneInvalidatedEventArgs.DirtyRect); } - public PixelPoint PointToScreen(Point point) => PlatformImpl?.PointToScreen(point) ?? default; + public PixelPoint? PointToScreen(Point point) => PlatformImpl?.PointToScreen(point); - public Point PointToClient(PixelPoint point) => PlatformImpl?.PointToClient(point) ?? default; + public Point? PointToClient(PixelPoint point) => PlatformImpl?.PointToClient(point); private void HandleScalingChanged(double scaling) => RenderScaling = LayoutHelper.ValidateScaling(scaling); diff --git a/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs index 1ec6ec2be5..f8bb8c3ed2 100644 --- a/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs @@ -1,4 +1,6 @@ -using Avalonia.Controls; +using System; +using Avalonia.Controls; +using Avalonia.Headless; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Media; @@ -128,6 +130,74 @@ namespace Avalonia.Base.UnitTests.Input Assert.Equal(new Point(1, 11), result); } + private IDisposable SetupCrossTreePositionRequest(PixelPoint topLevelPosition, out PointerEventArgs pointerEvent, out Control elementA, out Control elementB) + { + var app = UnitTestApplication.Start(new TestServices( + inputManager: new InputManager(), + renderInterface: new HeadlessPlatformRenderInterface())); + + var renderer = new Mock(); + var deviceMock = CreatePointerDeviceMock(); + var impl1 = CreateTopLevelImplMock(); + // Mocked position: topLevelPosition + impl1.Setup(w => w.PointToScreen(default)).Returns(p => (PixelPoint.FromPoint(p, 1) + topLevelPosition)); + + elementA = new Border(); + PointerEventArgs? moveEventArgs = null; + + elementA.PointerMoved += (s, e) => moveEventArgs = e; + var root1 = CreateInputRoot(impl1.Object, elementA, renderer.Object); + + SetMove(deviceMock, root1.InputRoot, elementA); + impl1.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root1)); + + Assert.NotNull(moveEventArgs); + pointerEvent = moveEventArgs; + + var impl2 = CreateTopLevelImplMock(); + // Mocked position: topLevelPosition * 2 + impl2.Setup(w => w.PointToClient(default)).Returns(p => (p - topLevelPosition - topLevelPosition).ToPoint(1)); + + elementB = new Border(); + var root2 = CreateInputRoot(impl2.Object, elementB, renderer.Object); + + return app; + } + + [Fact] + public void GetPosition_Should_Support_Cross_Tree_Requests() + { + var topLevelOffset = new PixelPoint(5, 0); + using (SetupCrossTreePositionRequest(topLevelOffset, out var pointerEvent, out _, out var elementB)) + { + Assert.Equal(topLevelOffset.ToPoint(1), pointerEvent.GetPosition(elementB)); + } + } + + [Fact] + public void GetPosition_Should_Return_Default_When_Cross_Tree_Source_Closed() + { + var topLevelOffset = new PixelPoint(5, 0); + using (SetupCrossTreePositionRequest(topLevelOffset, out var pointerEvent, out var elementA, out var elementB)) + { + ((PresentationSource)elementA.PresentationSource!).Dispose(); + + Assert.Equal(default, pointerEvent.GetPosition(elementB)); + } + } + + [Fact] + public void GetPosition_Should_Return_Default_When_Cross_Tree_Target_Closed() + { + var topLevelOffset = new PixelPoint(5, 0); + using (SetupCrossTreePositionRequest(topLevelOffset, out var pointerEvent, out _, out var elementB)) + { + ((PresentationSource)elementB.PresentationSource!).Dispose(); + + Assert.Equal(default, pointerEvent.GetPosition(elementB)); + } + } + [Fact] public void Mouse_Pointer_Should_Set_Focus_On_Pointer_Pressed() { diff --git a/tests/Avalonia.RenderTests/TestRenderRoot.cs b/tests/Avalonia.RenderTests/TestRenderRoot.cs index 602c5c6e8b..4bbe560fb6 100644 --- a/tests/Avalonia.RenderTests/TestRenderRoot.cs +++ b/tests/Avalonia.RenderTests/TestRenderRoot.cs @@ -67,9 +67,9 @@ namespace Avalonia.Skia.RenderTests { } - public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling); + public Point? PointToClient(PixelPoint point) => point.ToPoint(RenderScaling); - public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling); + public PixelPoint? PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling); public IFocusManager? FocusManager { get; } public IPlatformSettings? PlatformSettings { get; } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 4400d77267..5a34d301df 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -113,9 +113,9 @@ namespace Avalonia.UnitTests { } - public Point PointToClient(PixelPoint p) => p.ToPoint(1); + public Point? PointToClient(PixelPoint p) => p.ToPoint(1); - public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); + public PixelPoint? PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); public void RegisterChildrenNames() From dcb607179b707a6c0e41a39fc697af43c00abc48 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 4 May 2026 12:25:57 +0000 Subject: [PATCH 86/86] fix android file props (#21307) --- .../Platform/Storage/AndroidStorageItem.cs | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index 2ee0236828..a5ceb02312 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -546,8 +546,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF { var projection = new[] { - MediaStore.IMediaColumns.Size, MediaStore.IMediaColumns.DateAdded, - MediaStore.IMediaColumns.DateModified + Document.ColumnSize, Document.ColumnLastModified }; using var cursor = Activity.ContentResolver!.Query(Uri, projection, null, null, null); @@ -555,7 +554,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF { try { - var columnIndex = cursor.GetColumnIndex(MediaStore.IMediaColumns.Size); + var columnIndex = cursor.GetColumnIndex(Document.ColumnSize); if (columnIndex != -1) { size = (ulong)cursor.GetLong(columnIndex); @@ -569,22 +568,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF try { - var columnIndex = cursor.GetColumnIndex(MediaStore.IMediaColumns.DateAdded); - if (columnIndex != -1) - { - var longValue = cursor.GetLong(columnIndex); - itemDate = longValue > 0 ? DateTimeOffset.FromUnixTimeMilliseconds(longValue) : null; - } - } - catch (Exception ex) - { - Logger.TryGet(LogEventLevel.Verbose, LogArea.AndroidPlatform)? - .Log(this, "File DateAdded metadata reader failed: '{Exception}'", ex); - } - - try - { - var columnIndex = cursor.GetColumnIndex(MediaStore.IMediaColumns.DateModified); + var columnIndex = cursor.GetColumnIndex(Document.ColumnLastModified); if (columnIndex != -1) { var longValue = cursor.GetLong(columnIndex); @@ -594,7 +578,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF catch (Exception ex) { Logger.TryGet(LogEventLevel.Verbose, LogArea.AndroidPlatform)? - .Log(this, "File DateAdded metadata reader failed: '{Exception}'", ex); + .Log(this, "File LastModified metadata reader failed: '{Exception}'", ex); } } }