diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a1d2625a47..6e727b7cde 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -26,7 +26,7 @@
## Obsoletions / Deprecations
-
+
## Fixed issues
@@ -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 b2a81dd55d..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)
@@ -1693,12 +1741,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
@@ -1867,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
@@ -1939,6 +2023,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.Window.WindowStateProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.AppBuilder.get_LifetimeOverride
@@ -2095,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)
@@ -2389,6 +2509,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Window.ExtendClientAreaToDecorationsChanged(System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Window.get_ExtendClientAreaChromeHints
@@ -2575,6 +2701,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)
@@ -2779,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)
@@ -3079,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)
@@ -3355,12 +3499,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
@@ -3529,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
@@ -3601,6 +3781,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.Window.WindowStateProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.AppBuilder.get_LifetimeOverride
@@ -3757,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)
@@ -4051,6 +4267,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.Window.ExtendClientAreaToDecorationsChanged(System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.Window.get_ExtendClientAreaChromeHints
@@ -4243,6 +4465,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)
@@ -4399,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)
@@ -4561,6 +4795,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0006
+ P:Avalonia.Platform.IWindowImpl.WindowStateGetterIsUsable
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0006
M:Avalonia.OpenGL.IGlPlatformSurfaceRenderTargetFactory.CanRenderToSurface(Avalonia.OpenGL.IGlContext,Avalonia.Platform.Surfaces.IPlatformRenderSurface)
@@ -4741,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)
@@ -4915,6 +5161,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0006
+ P:Avalonia.Platform.IWindowImpl.WindowStateGetterIsUsable
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0006
M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)
diff --git a/build/MicroCOM.props b/build/MicroCOM.props
new file mode 100644
index 0000000000..1efa4b2ef3
--- /dev/null
+++ b/build/MicroCOM.props
@@ -0,0 +1,26 @@
+
+
+
+
+
+ $(MSBuildThisFileDirectory)/../../MicroCom
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/build.md b/docs/build.md
index 2285e56d01..bd8ae3fc02 100644
--- a/docs/build.md
+++ b/docs/build.md
@@ -42,7 +42,7 @@ dotnet run
If you want to open Avalonia in Visual Studio you have two options:
-- Avalonia.sln: This contains the whole of Avalonia in including desktop, mobile and web. You must have a number of dotnet workloads installed in order to build everything in this solution
+- Avalonia.slnx: This contains the whole of Avalonia in including desktop, mobile and web. You must have a number of dotnet workloads installed in order to build everything in this solution
- Avalonia.Desktop.slnf: This solution filter opens only the parts of Avalonia required to run on desktop. This requires no extra workloads to be installed.
Avalonia requires Visual Studio 2022 or newer. The free Visual Studio Community edition works fine.
diff --git a/external/Avalonia.DBus b/external/Avalonia.DBus
index f91a822c25..864a052828 160000
--- a/external/Avalonia.DBus
+++ b/external/Avalonia.DBus
@@ -1 +1 @@
-Subproject commit f91a822c258476f185e51112388775591e6ef9d6
+Subproject commit 864a05282841bf04006890f04d11d60d1a046aa9
diff --git a/native/Avalonia.Native/README.md b/native/Avalonia.Native/README.md
new file mode 100644
index 0000000000..47e06b996e
--- /dev/null
+++ b/native/Avalonia.Native/README.md
@@ -0,0 +1,87 @@
+# Avalonia.Native
+
+This project implements the macOS native platform backend for Avalonia using Objective-C++ and COM (Component Object Model) interfaces.
+
+
+## COM Reference Management
+
+This codebase uses COM for cross-boundary object lifetime management. COM types are typically prefixed with `IAvn` (interfaces) or `Avn` (implementations). All COM objects ultimately derive from `IUnknown` and use reference counting (`AddRef`/`Release`).
+
+**No raw COM pointer should ever be stored in class fields, instance variables, closures, or containers.** Raw COM pointers are only acceptable as function parameters and local variables with the lifetime of the current function call.
+
+### COM Return Value Convention
+
+By convention, any COM interface reference returned from a COM call has its reference counter incremented — it is the caller's responsibility to release it. With the `IFoo* GetFoo()` pattern it is hard to track this correctly, so all methods in `avn.idl` should use the out-parameter pattern instead:
+
+```cpp
+// WRONG — easy to leak or forget Release:
+IFoo* GetFoo();
+
+// CORRECT — use HRESULT + out-parameter:
+HRESULT GetFoo(IFoo** ppv);
+```
+# Smart pointer types
+
+### Objective-C Objects
+
+For Objective-C objects, reference counting is provided by the compiler. Objective-C pointers _look_ like raw pointers, however every `NSObject*` reference is properly ref-counted by ARC (Automatic Reference Counting). No manual wrapping is needed for `NSObject*` fields.
+
+### COM Smart Pointer Types
+
+All retained COM references must use one of the following wrappers defined in `inc/comimpl.h`:
+
+#### `ComPtr` — Owning Reference
+
+Use for any COM pointer that the holder needs to keep alive.
+
+```cpp
+ComPtr _window; // correct
+IAvnWindow* _window; // WRONG — raw COM pointer in a field
+```
+
+#### `ComObjectWeakPtr` — Non-Owning Weak Reference
+
+Use for intentional non-owning references to `ComObject`-derived objects (internal implementations). Allows safely referencing COM objects without extending their lifetime.
+
+```cpp
+ComObjectWeakPtr _parent; // correct
+WindowBaseImpl* _parent; // WRONG
+```
+
+Access weak references with `tryGet()`, which returns a `ComPtr` (null if the object was destroyed):
+
+```cpp
+auto parent = _parent.tryGet();
+if (parent) {
+ parent->DoSomething();
+}
+```
+
+#### `ComStaticPtr` — Process-Lifetime Static Reference
+
+Use for static/global COM singletons that must live for the entire process lifetime. Intentionally does **not** Release in its destructor to avoid crashes during app teardown.
+
+```cpp
+static ComStaticPtr GlDisplay; // correct
+static IAvnGlDisplay* GlDisplay; // WRONG
+```
+
+Assign via `set()`:
+
+```cpp
+GlDisplay.set(comnew());
+```
+
+### `comnew(args...)` — COM Object Factory
+
+A convenience template similar to `std::make_shared`. Creates a new COM object and returns it wrapped in a `ComPtr` with correct ownership (no double-AddRef):
+
+```cpp
+// Instead of:
+ComPtr cursor(new Cursor(nsCursor), true);
+
+// Write:
+ComPtr cursor = comnew(nsCursor);
+```
+
+This works because `new T()` on a `ComObject`-derived type starts with refcount=1, and `comnew` wraps it in a `ComPtr` that takes ownership without an additional `AddRef`.
diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h
index e16adb1ec2..0b09ee66fb 100644
--- a/native/Avalonia.Native/inc/comimpl.h
+++ b/native/Avalonia.Native/inc/comimpl.h
@@ -8,6 +8,7 @@
#include
#include
+#include
/**
START_COM_CALL causes AddRef to be called at the beginning of a function.
@@ -110,6 +111,11 @@ public:
TInterface** getPPV()
{
+ if(_obj)
+ {
+ _obj->Release();
+ _obj = nullptr;
+ }
return &_obj;
}
@@ -138,6 +144,78 @@ public:
}
};
+template
+ComPtr comnew(Args&&... args)
+{
+ return ComPtr(new T(std::forward(args)...), true);
+}
+
+/**
+ * ComStaticPtr is a COM smart pointer for process-lifetime static singletons.
+ * It AddRef/Release on set/reset, but intentionally does NOT Release in its destructor
+ * to avoid crashes during app teardown when destruction order is undefined.
+ */
+template
+class ComStaticPtr
+{
+private:
+ TInterface* _obj = nullptr;
+public:
+ ComStaticPtr() = default;
+ ~ComStaticPtr() = default;
+
+ ComStaticPtr(const ComPtr& ptr)
+ {
+ _obj = ptr;
+ if (_obj)
+ _obj->AddRef();
+ }
+
+ ComStaticPtr(const ComStaticPtr&) = delete;
+ ComStaticPtr(ComStaticPtr&&) = delete;
+ ComStaticPtr& operator=(const ComStaticPtr&) = delete;
+ ComStaticPtr& operator=(ComStaticPtr&&) = delete;
+
+ void set(TInterface* obj)
+ {
+ if (_obj == obj)
+ return;
+ if (_obj)
+ _obj->Release();
+ _obj = obj;
+ if (_obj)
+ _obj->AddRef();
+ }
+
+ void setNoAddRef(TInterface* obj)
+ {
+ if (_obj)
+ _obj->Release();
+ _obj = obj;
+ }
+
+ void reset()
+ {
+ if (_obj)
+ {
+ _obj->Release();
+ _obj = nullptr;
+ }
+ }
+
+ TInterface* getRaw() const { return _obj; }
+
+ template ComPtr getComPtr()
+ {
+ if (_obj == nullptr)
+ return nullptr;
+ return dynamic_cast(_obj);
+ }
+
+ operator TInterface*() const { return _obj; }
+ TInterface* operator->() const { return _obj; }
+};
+
class ComObjectWeakRefToken
{
public:
@@ -249,6 +327,9 @@ public:
_rawPtr = nullptr;
_token = nullptr;
}
+
+ bool operator==(const ComObjectWeakPtr& other) const { return _rawPtr == other._rawPtr; }
+ bool operator!=(const ComObjectWeakPtr& other) const { return _rawPtr != other._rawPtr; }
};
#define FORWARD_IUNKNOWN() \
diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm
index 78a40b215e..b6964195e1 100644
--- a/native/Avalonia.Native/src/OSX/AvnString.mm
+++ b/native/Avalonia.Native/src/OSX/AvnString.mm
@@ -79,16 +79,14 @@ public:
class AvnStringArrayImpl : public virtual ComSingleObject
{
private:
- std::vector> _list;
+ std::vector> _list;
public:
FORWARD_IUNKNOWN()
AvnStringArrayImpl(NSArray* array)
{
for(int c = 0; c < [array count]; c++)
{
- ComPtr s;
- *s.getPPV() = new AvnStringImpl([array objectAtIndex:c]);
- _list.push_back(s);
+ _list.push_back(comnew([array objectAtIndex:c]));
}
}
@@ -96,17 +94,14 @@ public:
{
for(int c = 0; c < [array count]; c++)
{
- ComPtr s;
- *s.getPPV() = new AvnStringImpl([array objectAtIndex:c].absoluteString);
+ auto s = comnew([array objectAtIndex:c].absoluteString);
_list.push_back(s);
}
}
AvnStringArrayImpl(NSString* string)
{
- ComPtr s;
- *s.getPPV() = new AvnStringImpl(string);
- _list.push_back(s);
+ _list.push_back(comnew(string));
}
virtual unsigned int GetCount() override
diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm
index 1e7c98cb3a..c4e715229b 100644
--- a/native/Avalonia.Native/src/OSX/AvnWindow.mm
+++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm
@@ -37,7 +37,7 @@
bool _isTransitioningToFullScreen;
bool _isTitlebarSession;
AvnMenu* _menu;
- IAvnAutomationPeer* _automationPeer;
+ ComPtr _automationPeer;
AvnAutomationNode* _automationNode;
}
@@ -144,8 +144,6 @@
{
auto appMenu = ::GetAppMenu();
- auto nativeAppMenu = dynamic_cast(appMenu);
-
[[appMenuItem menu] removeItem:appMenuItem];
if(_menu != nullptr)
@@ -153,9 +151,9 @@
[_menu setHasGlobalMenuItem:false];
}
- [nativeAppMenu->GetNative() addItem:appMenuItem];
+ [appMenu->GetNative() addItem:appMenuItem];
- [NSApp setMenu:nativeAppMenu->GetNative()];
+ [NSApp setMenu:appMenu->GetNative()];
}
}
diff --git a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm
index bdfc1be62f..d0b62d9d55 100644
--- a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm
+++ b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm
@@ -311,7 +311,9 @@ HRESULT TopLevelImpl::BeginDragAndDropOperation(
for (auto i = 0; i < itemCount; ++i)
{
- auto item = source->GetItem(i);
+ ComPtr item;
+ if(S_OK != source->GetItem(i, item.getPPV()))
+ continue;
auto writeableItem = [[WriteableClipboardItem alloc] initWithItem:item source:source];
auto draggingItem = [[NSDraggingItem alloc] initWithPasteboardWriter:writeableItem];
[draggingItem setDraggingFrame:dragItemRect contents:dragItemImage];
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
index 873a520d6d..2fbbf60bba 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
@@ -101,7 +101,7 @@ protected:
AutoFitContentView *StandardContainer;
AvnPoint lastPositionSet;
bool _shown;
- std::list _children;
+ std::list> _children;
public:
ComObjectWeakPtr Parent = nullptr;
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index 551ea4c2e0..2a1de62b05 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -27,7 +27,7 @@ WindowBaseImpl::~WindowBaseImpl() {
}
WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel) : TopLevelImpl(events) {
- _children = std::list();
+ _children = std::list>();
_shown = false;
_inResize = false;
BaseEvents = events;
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index f42b7c27cb..548d7aaf09 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -100,11 +100,14 @@ void WindowImpl::ZOrderChildWindows()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
- auto window = (*iterator)->Window;
+ auto child = (*iterator).tryGet();
+ if (!child) continue;
+
+ auto window = child->Window;
// #9565: Only bring window to front if it's on the currently active space
if ([window isOnActiveSpace]) {
- (*iterator)->BringToFront();
+ child->BringToFront();
}
}
}
@@ -113,7 +116,8 @@ bool WindowImpl::CanBecomeKeyWindow()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
- if((*iterator)->IsModal())
+ auto child = (*iterator).tryGet();
+ if(child && child->IsModal())
{
return false;
}
diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm
index b42dc22f7a..6eee49f619 100644
--- a/native/Avalonia.Native/src/OSX/automation.mm
+++ b/native/Avalonia.Native/src/OSX/automation.mm
@@ -8,7 +8,7 @@
@implementation AvnAccessibilityElement
{
- IAvnAutomationPeer* _peer;
+ ComPtr _peer;
AvnAutomationNode* _node;
NSMutableArray* _children;
NSArray* _attributeNames;
@@ -70,7 +70,7 @@
return [NSString stringWithFormat:@"%@ '%@' (%p)",
GetNSStringAndRelease(_peer->GetClassName()),
GetNSStringAndRelease(_peer->GetName()),
- _peer];
+ _peer.getRaw()];
}
- (IAvnAutomationPeer *)peer
@@ -317,7 +317,30 @@
- (id)accessibilityParent
{
auto parentPeer = _peer->GetParent();
- return parentPeer ? [AvnAccessibilityElement acquire:parentPeer] : [NSApplication sharedApplication];
+
+ if (parentPeer == nullptr)
+ return [NSApplication sharedApplication];
+
+ // When the parent is a root provider, return the AvnView (content view)
+ // rather than the AvnWindow. macOS accessibility requires that the parent
+ // chain is consistent with the children chain: AvnView exposes these
+ // elements as its accessibilityChildren, so the elements must report
+ // AvnView as their accessibilityParent. A mismatch causes macOS to be
+ // unable to resolve AXUIElementRefs back to the correct object, which
+ // makes setter calls like AXUIElementSetAttributeValue silently land on
+ // AvnView instead of the target AvnAccessibilityElement.
+ if (parentPeer->IsRootProvider())
+ {
+ auto window = parentPeer->RootProvider_GetWindow();
+ if (window != nullptr)
+ {
+ auto holder = dynamic_cast(window);
+ if (holder != nullptr)
+ return holder->GetNSView();
+ }
+ }
+
+ return [AvnAccessibilityElement acquire:parentPeer];
}
- (id)accessibilityTopLevelUIElement
@@ -403,7 +426,11 @@
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector
{
- if (selector == @selector(accessibilityPerformShowMenu))
+ if (selector == @selector(setAccessibilityValue:))
+ {
+ return _peer->IsValueProvider() || _peer->IsRangeValueProvider();
+ }
+ else if (selector == @selector(accessibilityPerformShowMenu))
{
return _peer->IsExpandCollapseProvider() && _peer->ExpandCollapseProvider_GetShowsMenu();
}
@@ -422,7 +449,7 @@
{
return _peer->IsRangeValueProvider();
}
-
+
return [super isAccessibilitySelectorAllowed:selector];
}
diff --git a/native/Avalonia.Native/src/OSX/cgl.mm b/native/Avalonia.Native/src/OSX/cgl.mm
index 4fc4064df1..e963322033 100644
--- a/native/Avalonia.Native/src/OSX/cgl.mm
+++ b/native/Avalonia.Native/src/OSX/cgl.mm
@@ -219,7 +219,7 @@ public:
}
};
-static IAvnGlDisplay* GlDisplay = new AvnGlDisplay();
+static ComStaticPtr GlDisplay(comnew());
extern IAvnGlDisplay* GetGlDisplay()
diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm
index 9786a64b27..f050a894c8 100644
--- a/native/Avalonia.Native/src/OSX/clipboard.mm
+++ b/native/Avalonia.Native/src/OSX/clipboard.mm
@@ -183,7 +183,9 @@ public:
for (auto i = 0; i < count; ++i)
{
- auto item = source->GetItem(i);
+ ComPtr item;
+ if(S_OK != source->GetItem(i, item.getPPV()))
+ continue;
auto writeableItem = [[WriteableClipboardItem alloc] initWithItem:item source:source];
[writeableItems addObject:writeableItem];
}
@@ -219,8 +221,8 @@ extern IAvnClipboard* CreateClipboard(NSPasteboard* pb)
@implementation WriteableClipboardItem
{
- IAvnClipboardDataItem* _item;
- IAvnClipboardDataSource* _source;
+ ComPtr _item;
+ ComPtr _source;
}
- (nonnull WriteableClipboardItem*) initWithItem:(nonnull IAvnClipboardDataItem*)item source:(nonnull IAvnClipboardDataSource*)source
@@ -229,9 +231,6 @@ extern IAvnClipboard* CreateClipboard(NSPasteboard* pb)
_item = item;
_source = source;
- // Each item references its source so it doesn't get disposed too early.
- source->AddRef();
-
return self;
}
@@ -283,8 +282,9 @@ NSString* TryConvertFormatToUti(NSString* format)
- (nonnull NSArray*) writableTypesForPasteboard:(nonnull NSPasteboard*)pasteboard
{
- auto formats = _item->ProvideFormats();
- if (formats == nullptr)
+ ComPtr formats;
+ auto hr = _item->ProvideFormats(formats.getPPV());
+ if (hr != S_OK || formats == nullptr)
return [NSArray array];
auto count = formats->GetCount();
@@ -304,7 +304,6 @@ NSString* TryConvertFormatToUti(NSString* format)
if (uti != nil)
[utis addObject:uti];
}
- formats->Release();
[utis addObject:GetAvnCustomDataType()];
@@ -323,8 +322,9 @@ NSString* TryConvertFormatToUti(NSString* format)
if ([type isEqualToString:GetAvnCustomDataType()])
return @"";
- ComPtr value(_item->GetValue([type UTF8String]), true);
- if (value.getRaw() == nullptr)
+ ComPtr value;
+ HRESULT hr = _item->GetValue([type UTF8String], value.getPPV());
+ if (hr != S_OK || value.getRaw() == nullptr)
return nil;
if (value->IsString())
@@ -336,19 +336,4 @@ NSString* TryConvertFormatToUti(NSString* format)
return [NSData dataWithBytesNoCopy:buffer length:length];
}
-- (void) dealloc
-{
- if (_item != nullptr)
- {
- _item->Release();
- _item = nullptr;
- }
-
- if (_source != nullptr)
- {
- _source->Release();
- _source = nullptr;
- }
-}
-
@end
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index a993784fc4..6564a457af 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -36,7 +36,8 @@ extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer();
extern IAvnNativeObjectsMemoryManagement* CreateMemoryManagementHelper();
extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu);
-extern IAvnMenu* GetAppMenu ();
+class AvnAppMenu;
+extern AvnAppMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void SetDockMenu(NSMenu* menu);
diff --git a/native/Avalonia.Native/src/OSX/cursor.h b/native/Avalonia.Native/src/OSX/cursor.h
index 75a9c3d2ad..cc7f765785 100644
--- a/native/Avalonia.Native/src/OSX/cursor.h
+++ b/native/Avalonia.Native/src/OSX/cursor.h
@@ -28,5 +28,4 @@ public:
}
};
-extern std::map s_cursorMap;
#endif /* cursor_h */
diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm
index 855a9a9abc..9947aa7c53 100644
--- a/native/Avalonia.Native/src/OSX/cursor.mm
+++ b/native/Avalonia.Native/src/OSX/cursor.mm
@@ -3,24 +3,24 @@
class CursorFactory : public ComSingleObject
{
- Cursor* arrowCursor = new Cursor([NSCursor arrowCursor]);
- Cursor* crossCursor = new Cursor([NSCursor crosshairCursor]);
- Cursor* resizeUpCursor = new Cursor([NSCursor resizeUpCursor]);
- Cursor* resizeDownCursor = new Cursor([NSCursor resizeDownCursor]);
- Cursor* resizeUpDownCursor = new Cursor([NSCursor resizeUpDownCursor]);
- Cursor* dragCopyCursor = new Cursor([NSCursor dragCopyCursor]);
- Cursor* openHandCursor = new Cursor([NSCursor openHandCursor]);
- Cursor* dragLinkCursor = new Cursor([NSCursor dragLinkCursor]);
- Cursor* pointingHandCursor = new Cursor([NSCursor pointingHandCursor]);
- Cursor* contextualMenuCursor = new Cursor([NSCursor contextualMenuCursor]);
- Cursor* IBeamCursor = new Cursor([NSCursor IBeamCursor]);
- Cursor* resizeLeftCursor = new Cursor([NSCursor resizeLeftCursor]);
- Cursor* resizeRightCursor = new Cursor([NSCursor resizeRightCursor]);
- Cursor* resizeWestEastCursor = new Cursor([NSCursor resizeLeftRightCursor]);
- Cursor* operationNotAllowedCursor = new Cursor([NSCursor operationNotAllowedCursor]);
- Cursor* noCursor = new Cursor([NSCursor arrowCursor], true);
+ ComPtr arrowCursor = comnew([NSCursor arrowCursor]);
+ ComPtr crossCursor = comnew([NSCursor crosshairCursor]);
+ ComPtr resizeUpCursor = comnew([NSCursor resizeUpCursor]);
+ ComPtr resizeDownCursor = comnew([NSCursor resizeDownCursor]);
+ ComPtr resizeUpDownCursor = comnew([NSCursor resizeUpDownCursor]);
+ ComPtr dragCopyCursor = comnew([NSCursor dragCopyCursor]);
+ ComPtr openHandCursor = comnew([NSCursor openHandCursor]);
+ ComPtr dragLinkCursor = comnew([NSCursor dragLinkCursor]);
+ ComPtr pointingHandCursor = comnew([NSCursor pointingHandCursor]);
+ ComPtr contextualMenuCursor = comnew([NSCursor contextualMenuCursor]);
+ ComPtr IBeamCursor = comnew([NSCursor IBeamCursor]);
+ ComPtr resizeLeftCursor = comnew([NSCursor resizeLeftCursor]);
+ ComPtr resizeRightCursor = comnew([NSCursor resizeRightCursor]);
+ ComPtr resizeWestEastCursor = comnew([NSCursor resizeLeftRightCursor]);
+ ComPtr operationNotAllowedCursor = comnew([NSCursor operationNotAllowedCursor]);
+ ComPtr noCursor = comnew([NSCursor arrowCursor], true);
- std::map s_cursorMap =
+ std::map> s_cursorMap =
{
{ CursorArrow, arrowCursor },
{ CursorAppStarting, arrowCursor },
diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h
index eb706d3e07..72c19eb120 100644
--- a/native/Avalonia.Native/src/OSX/menu.h
+++ b/native/Avalonia.Native/src/OSX/menu.h
@@ -29,8 +29,8 @@ class AvnAppMenuItem : public ComSingleObject
{
private:
NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
- IAvnActionCallback* _callback;
- IAvnPredicateCallback* _predicate;
+ ComPtr _callback;
+ ComPtr _predicate;
bool _isCheckable;
public:
diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm
index 1adbe093bb..a67c96a796 100644
--- a/native/Avalonia.Native/src/OSX/menu.mm
+++ b/native/Avalonia.Native/src/OSX/menu.mm
@@ -35,7 +35,7 @@
@implementation AvnMenuItem
{
- AvnAppMenuItem* _item;
+ ComObjectWeakPtr _item;
}
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem
@@ -61,13 +61,19 @@
{
return YES;
}
+ auto item = _item.tryGet();
+ if(item == nullptr)
+ return NO;
- return _item->EvaluateItemEnabled();
+ return item->EvaluateItemEnabled();
}
- (void)didSelectItem:(nullable id)sender
{
- _item->RaiseOnClicked();
+ auto item = _item.tryGet();
+ if(item == nullptr)
+ return;
+ item->RaiseOnClicked();
}
@end
@@ -478,17 +484,15 @@ extern IAvnMenuItem* CreateAppMenuItemSeparator()
}
}
-static IAvnMenu* s_appMenu = nullptr;
+static ComStaticPtr s_appMenu;
static NSMenuItem* s_appMenuItem = nullptr;
extern void SetAppMenu(IAvnMenu *menu)
{
- s_appMenu = menu;
+ s_appMenu.set(dynamic_cast(menu));
if(s_appMenu != nullptr)
{
- auto nativeMenu = dynamic_cast(s_appMenu);
-
auto currentMenu = [s_appMenuItem menu];
if (currentMenu != nullptr)
@@ -496,7 +500,7 @@ extern void SetAppMenu(IAvnMenu *menu)
[currentMenu removeItem:s_appMenuItem];
}
- s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0];
+ s_appMenuItem = [s_appMenu->GetNative() itemAtIndex:0];
if (currentMenu == nullptr)
{
@@ -524,9 +528,9 @@ extern void SetServicesMenu (IAvnMenu* menu)
[NSApplication sharedApplication].servicesMenu = nativeMenu->GetNative();
}
-extern IAvnMenu* GetAppMenu ()
+extern AvnAppMenu* GetAppMenu ()
{
- return s_appMenu;
+ return s_appMenu.getRaw();
}
extern NSMenuItem* GetAppMenuItem ()
diff --git a/native/Avalonia.Native/src/OSX/metal.mm b/native/Avalonia.Native/src/OSX/metal.mm
index 517872b147..3da1282306 100644
--- a/native/Avalonia.Native/src/OSX/metal.mm
+++ b/native/Avalonia.Native/src/OSX/metal.mm
@@ -304,7 +304,7 @@ public:
}
};
-static AvnMetalDisplay* _display = new AvnMetalDisplay();
+static ComStaticPtr _display(comnew());
extern IAvnMetalDisplay* GetMetalDisplay()
{
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 41c5cbe0cf..b9e27bdfcc 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -481,7 +481,7 @@ partial class Build : NukeBuild
.SetProperty("AvaloniaVersion", Parameters.Version)
.SetProperty("NuGetPackageRoot", nugetCacheDirectory)
.SetPackageDirectory(nugetCacheDirectory)
- .SetProjectFile(buildTestsDirectory / "BuildTests.sln")
+ .SetProjectFile(buildTestsDirectory / "BuildTests.slnx")
.SetProcessAdditionalArguments("--nodeReuse:false"));
// Standard compilation - should have compiled XAML
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index c4d20ad4b7..c58a2b894b 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -7,11 +7,11 @@
$(NoWarn);CS0649;CA1847
1
$(AvsCurrentTargetFramework)
+ true
-
@@ -22,6 +22,8 @@
+
+
diff --git a/readme.md b/readme.md
index 08fd014798..9ed6c59f90 100644
--- a/readme.md
+++ b/readme.md
@@ -1,10 +1,8 @@
[](https://github.com/sponsors/AvaloniaUI)
-[](https://avaloniaui.net?utm_source=github&utm_medium=referral&utm_content=readme_link)
+[](https://avaloniaui.net?utm_source=github&utm_medium=referral&utm_content=readme_link)
-[](https://t.me/Avalonia)
- [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [](#backers) [](#sponsors) [](https://github.com/sponsors/AvaloniaUI) 
-
-[](https://www.nuget.org/packages/Avalonia) [](https://www.nuget.org/packages/Avalonia) 
+ [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) 
+[](https://www.nuget.org/packages/Avalonia) [](https://www.nuget.org/packages/Avalonia)
## 📖 About
@@ -14,26 +12,33 @@ Considered by many to be the spiritual successor to WPF, Avalonia UI provides a
For those seeking a cross-platform WPF, we have created [Avalonia XPF]([https://avaloniaui.net/xpf](https://avaloniaui.net/xpf?utm_source=github&utm_medium=referral&utm_content=readme_link)), enabling WPF applications to run on macOS and Linux with little to no code changes. Avalonia XPF is a commercial product and is licensed per-app, per-platform.
-#### Blog
+### Blog
To see the latest announcements and read about the state of Avalonia, check out the [Avalonia UI Blog](https://www.avaloniaui.net/blog?utm_source=github&utm_medium=referral&utm_content=readme_link).
-#### Breaking Changes
-You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been.
+### Sponsorship
+Avalonia development is supported by the generous sponsorship of [Devolutions](https://devolutions.net/?utm_source=pr&utm_medium=partnership&utm_campaign=avalonia&utm_id=C086&member_status=responded).
-#### Awesome Avalonia
-[Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
+
+
+
+
+
+---
## 🚀 Getting Started
-See our [Get Started](https://avaloniaui.net/gettingstarted?utm_source=github&utm_medium=referral&utm_content=readme_link) guide to begin developing apps with Avalonia UI.
+See our [Get Started](https://docs.avaloniaui.net/docs/get-started?utm_source=github&utm_medium=referral&utm_content=readme_link) guide to begin developing apps with Avalonia UI.
+
+### Visual Studio Code
+The [Avalonia for Code](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.vscode-avalonia) extension gives you everything you need to build cross-platform .NET apps. XAML previewing, code completion, diagnostics, and code navigation, all inside the world's most popular editor. It's the fastest way to get started with Avalonia.
-### Visual Studio
-The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaVS) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://docs.avaloniaui.net/docs/getting-started).
+### Visual Studio 2022 & 2026
+The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaVS) contains project and control templates, a live XAML previewer, code completion, and full diagnostics directly into Visual Studio.
### JetBrains Rider
-[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia.
+[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) supports for Avalonia.
-Code completion, inspections and refactorings are supported out of the box, for XAML previewer add `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin.
+Code completion, inspections and refactorings are supported out of the box. To enable previewing XAML, you will require a 3rd party, community-maintained extension: `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin.
### Avalonia Packages
Avalonia is delivered via NuGet package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/
@@ -43,79 +48,58 @@ Use these commands in the Package Manager console to install Avalonia manually:
Install-Package Avalonia
Install-Package Avalonia.Desktop
```
-
-## Showcase
-[](https://avaloniaui.net/showcase?utm_source=github&utm_medium=referral&utm_content=readme_link)
-
-See what others have built with Avalonia UI on our [Showcase](https://avaloniaui.net/showcase?utm_source=github&utm_medium=referral&utm_content=readme_link). We welcome submissions!
-
-## Sponsors
-Avalonia development is supported by the generous sponsorship of [Devolutions](https://devolutions.net/?utm_source=pr&utm_medium=partnership&utm_campaign=avalonia&utm_id=C086&member_status=responded).
-
-
-
-
-## Community
-Join our community hub to get early access to upcoming features, share your thoughts, and connect directly with the Avalonia team.
-[](https://avaloniaui.community)
+---
## Bleeding Edge Builds
We also have a [nightly build](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) which tracks the current state of master. Although these packages are less stable than the release on NuGet.org, you'll get all the latest features and bugfixes right away and many of our users actually prefer this feed!
+---
## Learning
### Documentation
Documentation can be found at https://docs.avaloniaui.net.
### Tutorials
-We also have a [tutorial](https://docs.avaloniaui.net/docs/getting-started/programming-with-avalonia) over there for newcomers.
+We also have a [tutorial](https://docs.avaloniaui.net/docs/get-started/starter-tutorial) over there for newcomers.
### Samples
We have a [range of samples](https://github.com/AvaloniaUI/Avalonia.Samples) to help you get started.
+### Breaking Changes
+You can also see what [breaking changes](https://docs.avaloniaui.net/docs/avalonia12-breaking-changes) between Avalonia 11 and Avalonia 12.
+
+---
## Building and Using
See the [build instructions here](docs/build.md).
+---
## Contributing
-This project exists thanks to all the people who contribute.
-
-
-Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a pull request.
+This project exists thanks to all the people who contribute. Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a pull request.
-## Code of Conduct
+### Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
-For more information see the [Code of Conduct](CODE_OF_CONDUCT.md).
+For more information, see the [Code of Conduct](CODE_OF_CONDUCT.md).
-## Licence
-
-Avalonia is licenced under the [MIT licence](licence.md).
-
-## Donate
-
-Donating to the project is a fantastic way to thank our valued contributors for their hard work. Your donations are shared among our community and awarded for significant contributions.
+---
+## Security
-If you need support see Commercial Support section below.
+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.
-Donate with BTC or use [Open Collective](https://opencollective.com/avalonia).
+You can report security vulnerabilities by emailing **[security@avaloniaui.net](mailto:security@avaloniaui.net)**.
-**BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx
+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.
-### Backers
+Please note that Avalonia does not operate a bug bounty programme.
-Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]
-
-
-
-## Commercial Support
-
-We have a range of [support plans available](https://avaloniaui.net/support?utm_source=github&utm_medium=referral&utm_content=readme_link) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process.
+---
+## Licence
-*Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)*
+Avalonia is licenced under the [MIT licence](licence.md).
-## Avalonia XPF - Our cross-platform WPF
+## XPF - cross-platform WPF
Unleash the full potential of your existing WPF apps with our cross-platform UI framework, enabling WPF apps to run on macOS and Linux without requiring expensive and risky rewrites.
-[](https://avaloniaui.net/xpf?utm_source=github&utm_medium=referral&utm_content=readme_ad)
+[](https://avaloniaui.net/xpf?utm_source=github&utm_medium=referral&utm_content=readme_ad)
diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
index 73b983f788..22a091449f 100644
--- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
+++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
@@ -5,6 +5,7 @@
$(AvsCurrentTargetFramework)
true
true
+ true
@@ -37,4 +38,5 @@
+
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index adea1b90fc..3a6d57801a 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -16,7 +16,7 @@
-
+
diff --git a/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs
index f7c87f56a3..bc8be3ea87 100644
--- a/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/CarouselPage/CareCompanionAppPage.xaml.cs
@@ -703,7 +703,7 @@ public partial class CareCompanionAppPage : UserControl
var home = BuildHomeTab();
home.Header = "Home";
- home.Icon = Geometry.Parse("M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z");
+ home.Icon = new PathIcon { Data = Geometry.Parse("M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z") };
tp.Pages = new ObservableCollection
{
@@ -729,7 +729,7 @@ public partial class CareCompanionAppPage : UserControl
=> new ContentPage
{
Header = header,
- Icon = Geometry.Parse(iconData),
+ Icon = new PathIcon { Data = Geometry.Parse(iconData) },
Background = new SolidColorBrush(BgLight),
Content = new StackPanel
{
diff --git a/samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml b/samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml
index b701ab89ba..d8d5f322c3 100644
--- a/samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml
+++ b/samples/ControlCatalog/Pages/CarouselPage/SanctuaryMainPage.xaml
@@ -93,7 +93,7 @@
- M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z
+
@@ -243,7 +243,7 @@
- M12 10.9c-.61 0-1.1.49-1.1 1.1s.49 1.1 1.1 1.1c.61 0 1.1-.49 1.1-1.1s-.49-1.1-1.1-1.1zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm2.19 12.19L6 18l3.81-8.19L18 6l-3.81 8.19z
+
@@ -260,7 +260,7 @@
- M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z
+
@@ -277,7 +277,7 @@
- M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z
+
diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
index 4cdde6b824..65f58d0444 100644
--- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
@@ -21,8 +21,6 @@ namespace ControlCatalog.Pages
DataFormat.CreateBytesApplicationFormat("controlcatalog-binary-data");
private INotificationManager? _notificationManager;
- private INotificationManager NotificationManager => _notificationManager
- ??= new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
private readonly DispatcherTimer _clipboardLastDataObjectChecker;
private DataTransfer? _storedDataTransfer;
@@ -107,7 +105,7 @@ namespace ControlCatalog.Pages
if (invalidFile.Count > 0)
{
- NotificationManager.Show(new Notification("Warning", "There is one o more invalid path.", NotificationType.Warning));
+ _notificationManager?.Show(new Notification("Warning", "There is one o more invalid path.", NotificationType.Warning));
}
if (files.Count > 0)
@@ -116,11 +114,11 @@ namespace ControlCatalog.Pages
foreach (var file in files)
dataTransfer.Add(DataTransferItem.Create(DataFormat.File, file));
await clipboard.SetDataAsync(dataTransfer);
- NotificationManager.Show(new Notification("Success", "Copy completed.", NotificationType.Success));
+ _notificationManager?.Show(new Notification("Success", "Copy completed.", NotificationType.Success));
}
else
{
- NotificationManager.Show(new Notification("Warning", "Any files to copy in Clipboard.", NotificationType.Warning));
+ _notificationManager?.Show(new Notification("Warning", "Any files to copy in Clipboard.", NotificationType.Warning));
}
}
}
@@ -179,6 +177,7 @@ namespace ControlCatalog.Pages
{
_clipboardLastDataObjectChecker.Start();
base.OnAttachedToVisualTree(e);
+ _notificationManager = new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
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..f3098dee0f 100644
--- a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml
@@ -8,6 +8,7 @@
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
M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z
M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z
+ M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20M16,11V18.1L13.9,16L11.1,18.8L8.3,16L11.1,13.2L9,11.1L16,11Z
@@ -33,6 +34,18 @@
IsChecked="True"
IsCheckedChanged="OnDynamicOverflowChanged" />
+
+
+
+
+
+
@@ -47,11 +60,11 @@
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
index 45a1c95527..19a451b55c 100644
--- a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
@@ -9,7 +9,13 @@ namespace ControlCatalog.Pages
public CommandBarDynamicOverflowPage()
{
InitializeComponent();
+ if (SecondaryVisibleCheck.IsChecked != true)
+ {
+ DemoBar.SecondaryCommands?.Remove(DemoSecondaryCommand);
+ }
+
((INotifyCollectionChanged)DemoBar.OverflowItems).CollectionChanged += OnOverflowChanged;
+ ((INotifyCollectionChanged)DemoBar.VisiblePrimaryCommands).CollectionChanged += OnOverflowChanged;
UpdateStatus();
}
@@ -29,6 +35,26 @@ namespace ControlCatalog.Pages
DemoBar.IsDynamicOverflowEnabled = DynamicOverflowCheck.IsChecked == true;
}
+ private void OnSecondaryVisibilityChanged(object? sender, RoutedEventArgs e)
+ {
+ if (DemoBar?.SecondaryCommands == null || DemoSecondaryCommand == null)
+ return;
+
+ bool shouldInclude = SecondaryVisibleCheck.IsChecked == true;
+ bool isIncluded = DemoBar.SecondaryCommands.Contains(DemoSecondaryCommand);
+
+ if (shouldInclude && !isIncluded)
+ {
+ DemoBar.SecondaryCommands.Add(DemoSecondaryCommand);
+ }
+ else if (!shouldInclude && isIncluded)
+ {
+ DemoBar.SecondaryCommands.Remove(DemoSecondaryCommand);
+ }
+
+ UpdateStatus();
+ }
+
private void OnOverflowChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateStatus();
@@ -36,10 +62,40 @@ namespace ControlCatalog.Pages
private void UpdateStatus()
{
- var total = DemoBar.PrimaryCommands.Count;
- var overflow = DemoBar.OverflowItems.Count;
- var visible = total - overflow;
- StatusText.Text = $"Showing {visible} of {total} commands, {overflow} in overflow";
+ int visiblePrimaryCommandCount = 0;
+ int visiblePrimarySeparatorCount = 0;
+ foreach (var item in DemoBar.VisiblePrimaryCommands)
+ {
+ if (item is CommandBarSeparator)
+ visiblePrimarySeparatorCount++;
+ else
+ visiblePrimaryCommandCount++;
+ }
+
+ int overflowCommandCount = 0;
+ int overflowSeparatorCount = 0;
+ bool hasSyntheticOverflowDivider = false;
+ foreach (var item in DemoBar.OverflowItems)
+ {
+ if (item is CommandBarSeparator separator)
+ {
+ overflowSeparatorCount++;
+ if (!DemoBar.PrimaryCommands.Contains(separator) &&
+ !DemoBar.SecondaryCommands.Contains(separator))
+ {
+ hasSyntheticOverflowDivider = true;
+ }
+ }
+ else
+ {
+ overflowCommandCount++;
+ }
+ }
+
+ StatusText.Text =
+ $"Visible primary: {visiblePrimaryCommandCount} commands, {visiblePrimarySeparatorCount} separators\n" +
+ $"Overflow items: {overflowCommandCount} commands, {overflowSeparatorCount} separators\n" +
+ $"Synthetic overflow divider: {(hasSyntheticOverflowDivider ? "present" : "absent")}";
}
}
}
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/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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Keyboard navigation in the overflow popup: Up/Down to move, Home/End to jump, Escape to close.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml.cs
new file mode 100644
index 0000000000..013614db7f
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarKeyboardPage.xaml.cs
@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+
+namespace ControlCatalog.Pages
+{
+ public partial class CommandBarKeyboardPage : UserControl
+ {
+ private readonly List _log = new();
+
+ public CommandBarKeyboardPage()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ Unloaded += OnUnloaded;
+ }
+
+ private void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ DemoBar.Opened += OnOpened;
+ DemoBar.Closed += OnClosed;
+
+ BtnCopy.GotFocus += OnItemFocused;
+ BtnPaste.GotFocus += OnItemFocused;
+ BtnBold.GotFocus += OnItemFocused;
+ BtnShare.GotFocus += OnItemFocused;
+ BtnDelete.GotFocus += OnItemFocused;
+ BtnExport.GotFocus += OnItemFocused;
+ }
+
+ private void OnUnloaded(object? sender, RoutedEventArgs e)
+ {
+ DemoBar.Opened -= OnOpened;
+ DemoBar.Closed -= OnClosed;
+
+ BtnCopy.GotFocus -= OnItemFocused;
+ BtnPaste.GotFocus -= OnItemFocused;
+ BtnBold.GotFocus -= OnItemFocused;
+ BtnShare.GotFocus -= OnItemFocused;
+ BtnDelete.GotFocus -= OnItemFocused;
+ BtnExport.GotFocus -= OnItemFocused;
+ }
+
+ private void OnOpened(object? sender, RoutedEventArgs e)
+ => AppendLog("Opened. Use arrow keys to navigate.");
+
+ private void OnClosed(object? sender, RoutedEventArgs e)
+ => AppendLog("Closed");
+
+ private void OnItemFocused(object? sender, FocusChangedEventArgs e)
+ {
+ var label = sender switch
+ {
+ CommandBarButton btn => btn.Label ?? "(unnamed)",
+ CommandBarToggleButton t => t.Label ?? "(unnamed)",
+ _ => sender?.GetType().Name ?? "?"
+ };
+
+ var method = e.NavigationMethod switch
+ {
+ NavigationMethod.Directional => "arrow key",
+ NavigationMethod.Tab => "Tab",
+ NavigationMethod.Pointer => "pointer",
+ _ => "unspecified"
+ };
+
+ AppendLog($"Focus: {label} ({method})");
+ }
+
+ private void OnOpenOverflow(object? sender, RoutedEventArgs e)
+ {
+ DemoBar.IsOpen = true;
+ }
+
+ private void OnClearLog(object? sender, RoutedEventArgs e)
+ {
+ _log.Clear();
+ FocusLogText.Text = "Log cleared.";
+ }
+
+ private void AppendLog(string message)
+ {
+ _log.Add(message);
+
+ if (_log.Count > 10)
+ _log.RemoveAt(0);
+
+ FocusLogText.Text = string.Join("\n", _log.Select((entry, i) => $"{i + 1,2}. {entry}"));
+ }
+ }
+}
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..b759c7b605 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()),
@@ -23,6 +23,7 @@ namespace ControlCatalog.Pages
("Features", "Overflow Menu", "Secondary commands appear in an overflow popup. Configure visibility and sticky behavior.", () => new CommandBarOverflowPage()),
("Features", "Dynamic Overflow", "IsDynamicOverflowEnabled moves primary commands to overflow as space shrinks.", () => new CommandBarDynamicOverflowPage()),
("Features", "Events & State", "Observe Opening, Opened, Closing, and Closed while tracking IsOpen, HasSecondaryCommands, and IsOverflowButtonVisible.", () => new CommandBarEventsPage()),
+ ("Features", "Keyboard Navigation", "Up/Down to move between overflow items, Home/End to jump to first/last, Escape to close and return focus.", () => new CommandBarKeyboardPage()),
};
public CommandBarPage()
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/DrawerPage/DrawerPageCustomizationPage.xaml b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml
index 4987e8979e..3adffed5f7 100644
--- a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml
+++ b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml
@@ -72,6 +72,14 @@
+
+
+
+
+
+
+
@@ -82,6 +90,14 @@
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs
index 243bc5868b..d948252385 100644
--- a/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DrawerPage/DrawerPageCustomizationPage.xaml.cs
@@ -1,8 +1,10 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
+using Avalonia.Layout;
using Avalonia.Media;
namespace ControlCatalog.Pages
@@ -143,7 +145,7 @@ namespace ControlCatalog.Pages
{
if (!_isLoaded)
return;
- DemoDrawer.DrawerIcon = Geometry.Parse(_iconPaths[IconCombo.SelectedIndex]);
+ DemoDrawer.DrawerIcon = new PathIcon { Data = Geometry.Parse(_iconPaths[IconCombo.SelectedIndex]) };
}
private void OnBackdropChanged(object? sender, SelectionChangedEventArgs e)
@@ -164,7 +166,7 @@ namespace ControlCatalog.Pages
if (!_isLoaded)
return;
if (ShowHeaderCheck.IsChecked == true)
- DemoDrawer.DrawerHeader = DrawerHeaderBorder;
+ DemoDrawer.DrawerHeader = HeaderTemplateCombo.SelectedIndex == 0 ? DrawerHeaderBorder : (object)"My Application";
else
DemoDrawer.DrawerHeader = null;
}
@@ -174,9 +176,128 @@ namespace ControlCatalog.Pages
if (!_isLoaded)
return;
if (ShowFooterCheck.IsChecked == true)
- DemoDrawer.DrawerFooter = DrawerFooterBorder;
+ {
+ DemoDrawer.DrawerFooter = FooterTemplateCombo.SelectedIndex switch
+ {
+ 1 => (object)"v12.0",
+ 2 => (object)"Avalonia",
+ _ => DrawerFooterBorder
+ };
+ }
else
+ {
DemoDrawer.DrawerFooter = null;
+ }
+ }
+
+ private void OnHeaderTemplateChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (!_isLoaded)
+ return;
+
+ switch (HeaderTemplateCombo.SelectedIndex)
+ {
+ case 1:
+ DemoDrawer.DrawerHeader = "My Application";
+ DemoDrawer.DrawerHeaderTemplate = new FuncDataTemplate((data, _) =>
+ new Border
+ {
+ Padding = new Avalonia.Thickness(16),
+ Child = new StackPanel
+ {
+ Spacing = 2,
+ Children =
+ {
+ new TextBlock { Text = data, FontSize = 18, FontWeight = FontWeight.SemiBold, Foreground = Brushes.White },
+ new TextBlock { Text = "Navigation", FontSize = 12, Foreground = Brushes.White, Opacity = 0.7 }
+ }
+ }
+ });
+ break;
+
+ case 2:
+ DemoDrawer.DrawerHeader = "My Application";
+ DemoDrawer.DrawerHeaderTemplate = new FuncDataTemplate((data, _) =>
+ {
+ var initial = data?.Length > 0 ? data[0].ToString().ToUpperInvariant() : "?";
+ var avatar = new Border
+ {
+ Width = 40,
+ Height = 40,
+ CornerRadius = new Avalonia.CornerRadius(20),
+ Background = new SolidColorBrush(Color.Parse("#1976D2")),
+ Child = new TextBlock
+ {
+ Text = initial,
+ FontSize = 18,
+ FontWeight = FontWeight.Bold,
+ Foreground = Brushes.White,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ };
+ var label = new TextBlock { Text = data, FontSize = 14, FontWeight = FontWeight.SemiBold, VerticalAlignment = VerticalAlignment.Center };
+ var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 };
+ row.Children.Add(avatar);
+ row.Children.Add(label);
+ return new Border { Padding = new Avalonia.Thickness(12), Child = row };
+ });
+ break;
+
+ default:
+ DemoDrawer.DrawerHeader = DrawerHeaderBorder;
+ DemoDrawer.DrawerHeaderTemplate = null;
+ break;
+ }
+ }
+
+ private void OnFooterTemplateChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (!_isLoaded)
+ return;
+
+ switch (FooterTemplateCombo.SelectedIndex)
+ {
+ case 1:
+ DemoDrawer.DrawerFooter = "v12.0";
+ DemoDrawer.DrawerFooterTemplate = new FuncDataTemplate((data, _) =>
+ new Border
+ {
+ Padding = new Avalonia.Thickness(12, 8),
+ Child = new Border
+ {
+ Padding = new Avalonia.Thickness(8, 4),
+ CornerRadius = new Avalonia.CornerRadius(4),
+ Background = new SolidColorBrush(Color.Parse("#1976D2")),
+ Child = new TextBlock { Text = data, FontSize = 11, Foreground = Brushes.White, FontWeight = FontWeight.SemiBold }
+ }
+ });
+ break;
+
+ case 2:
+ DemoDrawer.DrawerFooter = "Avalonia";
+ DemoDrawer.DrawerFooterTemplate = new FuncDataTemplate((data, _) =>
+ {
+ var icon = new PathIcon
+ {
+ Width = 14,
+ Height = 14,
+ Data = Geometry.Parse("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"),
+ Opacity = 0.5
+ };
+ var label = new TextBlock { Text = data, FontSize = 12, Opacity = 0.6, VerticalAlignment = VerticalAlignment.Center };
+ var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 6 };
+ row.Children.Add(icon);
+ row.Children.Add(label);
+ return new Border { Padding = new Avalonia.Thickness(14, 10), Child = row };
+ });
+ break;
+
+ default:
+ DemoDrawer.DrawerFooter = DrawerFooterBorder;
+ DemoDrawer.DrawerFooterTemplate = null;
+ break;
+ }
}
private void OnMenuItemClick(object? sender, RoutedEventArgs e)
diff --git a/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml b/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml
index 1e9106ccfe..22320fbc8d 100644
--- a/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml
+++ b/samples/ControlCatalog/Pages/DrawerPage/EcoTrackerAppPage.xaml
@@ -52,13 +52,9 @@
- M12 3C9 6 6 9 6 13C6 17.4 8.7 21 12 22C15.3 21 18 17.4 18 13C18 9 15 6 12 3Z
+
-
-
-
-
-
diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs
index 2906091daa..d8b89e0ecb 100644
--- a/samples/ControlCatalog/Pages/GesturePage.cs
+++ b/samples/ControlCatalog/Pages/GesturePage.cs
@@ -1,214 +1,39 @@
using System;
-using Avalonia;
using Avalonia.Controls;
-using Avalonia.Input;
-using Avalonia.LogicalTree;
-using Avalonia.Rendering.Composition;
-using Avalonia.Utilities;
+using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public partial class GesturePage : UserControl
{
- private bool _isInit;
- private double _currentScale;
-
- public GesturePage()
- {
- InitializeComponent();
- }
-
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ private static readonly (string Group, string Title, string Description, Func Factory)[] Demos =
{
- base.OnAttachedToVisualTree(e);
-
- if (_isInit)
- {
- return;
- }
-
- _isInit = true;
-
- SetPullHandlers(TopPullZone, false);
- SetPullHandlers(BottomPullZone, true);
- SetPullHandlers(RightPullZone, true);
- SetPullHandlers(LeftPullZone, false);
-
- var image = PinchImage;
- SetPinchHandlers(image);
-
- var reset = ResetButton;
+ ("Touch / Pen", "Pull Gesture",
+ "Press and drag from colored border zones. A green ball tracks the pull delta and springs back on release.",
+ () => new GesturePullPage()),
- reset.Click += (_, _) =>
- {
- var compositionVisual = ElementComposition.GetElementVisual(image);
+ ("Multi Touch", "Pinch / Zoom",
+ "Pinch to scale an image using composition visuals. Scroll to pan when zoomed in.",
+ () => new GesturePinchZoomPage()),
- if (compositionVisual != null)
- {
- _currentScale = 1;
- compositionVisual.Scale = new(1, 1, 1);
- compositionVisual.Offset = default;
- image.InvalidateMeasure();
- }
- };
+ ("Multi Touch", "Pinch / Rotation",
+ "Pinch to rotate a rectangle. The Angle property from the pinch event drives a RotateTransform.",
+ () => new GesturePinchRotationPage()),
- RotationGesture.AddHandler(InputElement.PinchEvent, (s, e) =>
- {
- AngleSlider.Value = e.Angle;
- });
- }
+ ("Touch / Pen / Mouse", "Swipe Gesture",
+ "Swipe horizontally or vertically. Configure direction, threshold, and mouse support. Shows live delta, velocity, and direction.",
+ () => new GestureSwipePage()),
+ };
- private void SetPinchHandlers(Control? control)
+ public GesturePage()
{
- if (control == null)
- {
- return;
- }
-
- _currentScale = 1;
- Vector3D currentOffset = default;
-
- CompositionVisual? compositionVisual = null;
-
- void InitComposition(Control visual)
- {
- if (compositionVisual != null)
- {
- return;
- }
-
- compositionVisual = ElementComposition.GetElementVisual(visual);
- }
-
- control.LayoutUpdated += (s, e) =>
- {
- InitComposition(control!);
- if (compositionVisual != null)
- {
- compositionVisual.Scale = new(_currentScale, _currentScale, 1);
-
- if (currentOffset == default)
- {
- currentOffset = compositionVisual.Offset;
- }
- }
- };
-
- control.AddHandler(InputElement.PinchEvent, (s, e) =>
- {
- InitComposition(control!);
-
- if (compositionVisual != null)
- {
- var scale = _currentScale * (float)e.Scale;
-
- if (scale <= 1)
- {
- scale = 1;
- compositionVisual.Offset = default;
- }
-
- compositionVisual.Scale = new(scale, scale, 1);
-
- e.Handled = true;
- }
- });
-
- control.AddHandler(InputElement.PinchEndedEvent, (s, e) =>
- {
- InitComposition(control!);
-
- if (compositionVisual != null)
- {
- _currentScale = compositionVisual.Scale.X;
- }
- });
-
- control.AddHandler(InputElement.ScrollGestureEvent, (s, e) =>
- {
- InitComposition(control!);
-
- if (compositionVisual != null && _currentScale != 1)
- {
- currentOffset += new Vector3D(e.Delta.X, e.Delta.Y, 0);
-
- var currentSize = control.Bounds.Size * _currentScale;
-
- currentOffset = new Vector3D(Math.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
- (float)Math.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height),
- 0);
-
- compositionVisual.Offset = currentOffset * -1;
-
- e.Handled = true;
- }
- });
+ InitializeComponent();
+ Loaded += OnLoaded;
}
- private void SetPullHandlers(Control control, bool inverse)
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
{
- var ball = control.FindLogicalDescendantOfType();
-
- Vector3D defaultOffset = default;
-
- CompositionVisual? ballCompositionVisual = null;
-
- if (ball != null)
- {
- InitComposition(ball);
- }
- else
- {
- return;
- }
-
- control.LayoutUpdated += (s, e) =>
- {
- InitComposition(ball!);
- if (ballCompositionVisual != null)
- {
- defaultOffset = ballCompositionVisual.Offset;
- }
- };
-
- control.AddHandler(InputElement.PullGestureEvent, (s, e) =>
- {
- Vector3D center = new((float)control.Bounds.Center.X, (float)control.Bounds.Center.Y, 0);
- InitComposition(ball!);
- if (ballCompositionVisual != null)
- {
- ballCompositionVisual.Offset = defaultOffset + new Vector3D(e.Delta.X * 0.4f, e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
-
- e.Handled = true;
- }
- });
-
- control.AddHandler(InputElement.PullGestureEndedEvent, (s, e) =>
- {
- InitComposition(ball!);
- if (ballCompositionVisual != null)
- {
- ballCompositionVisual.Offset = defaultOffset;
- }
- });
-
- void InitComposition(Control control)
- {
- ballCompositionVisual = ElementComposition.GetElementVisual(ball);
-
- if (ballCompositionVisual != null)
- {
- var offsetAnimation = ballCompositionVisual.Compositor.CreateVector3KeyFrameAnimation();
- offsetAnimation.Target = "Offset";
- offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
- offsetAnimation.Duration = TimeSpan.FromMilliseconds(100);
-
- var implicitAnimations = ballCompositionVisual.Compositor.CreateImplicitAnimationCollection();
- implicitAnimations["Offset"] = offsetAnimation;
-
- ballCompositionVisual.ImplicitAnimations = implicitAnimations;
- }
- }
+ await SampleNav.PushAsync(NavigationDemoHelper.CreateGalleryHomePage(SampleNav, Demos), null);
}
}
}
diff --git a/samples/ControlCatalog/Pages/GesturePage.xaml b/samples/ControlCatalog/Pages/GesturePage.xaml
index 00d36d6cea..bce18eab1d 100644
--- a/samples/ControlCatalog/Pages/GesturePage.xaml
+++ b/samples/ControlCatalog/Pages/GesturePage.xaml
@@ -1,170 +1,11 @@
-
-
-
- Pull Gexture (Touch / Pen)
-
-
-
- Pull from colored rectangles
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pinch/Zoom Gexture (Multi Touch)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pinch/Rotation Gexture (Multi Touch)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/Gestures/GesturePinchRotationPage.xaml b/samples/ControlCatalog/Pages/Gestures/GesturePinchRotationPage.xaml
new file mode 100644
index 0000000000..b46af74c3b
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GesturePinchRotationPage.xaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/Gestures/GesturePinchRotationPage.xaml.cs b/samples/ControlCatalog/Pages/Gestures/GesturePinchRotationPage.xaml.cs
new file mode 100644
index 0000000000..55d5e619ee
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GesturePinchRotationPage.xaml.cs
@@ -0,0 +1,33 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+
+namespace ControlCatalog.Pages
+{
+ public partial class GesturePinchRotationPage : UserControl
+ {
+ private bool _isInit;
+
+ public GesturePinchRotationPage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ if (_isInit)
+ {
+ return;
+ }
+
+ _isInit = true;
+
+ RotationGesture.AddHandler(InputElement.PinchEvent, (s, e) =>
+ {
+ AngleSlider.Value = e.Angle;
+ });
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/Gestures/GesturePinchZoomPage.xaml b/samples/ControlCatalog/Pages/Gestures/GesturePinchZoomPage.xaml
new file mode 100644
index 0000000000..6932741e5b
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GesturePinchZoomPage.xaml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/Gestures/GesturePinchZoomPage.xaml.cs b/samples/ControlCatalog/Pages/Gestures/GesturePinchZoomPage.xaml.cs
new file mode 100644
index 0000000000..9906a646c4
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GesturePinchZoomPage.xaml.cs
@@ -0,0 +1,134 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Rendering.Composition;
+
+namespace ControlCatalog.Pages
+{
+ public partial class GesturePinchZoomPage : UserControl
+ {
+ private bool _isInit;
+ private double _currentScale;
+
+ public GesturePinchZoomPage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ if (_isInit)
+ {
+ return;
+ }
+
+ _isInit = true;
+
+ var image = PinchImage;
+ SetPinchHandlers(image);
+
+ ResetButton.Click += (_, _) =>
+ {
+ var compositionVisual = ElementComposition.GetElementVisual(image);
+
+ if (compositionVisual != null)
+ {
+ _currentScale = 1;
+ compositionVisual.Scale = new(1, 1, 1);
+ compositionVisual.Offset = default;
+ image.InvalidateMeasure();
+ }
+ };
+ }
+
+ private void SetPinchHandlers(Control? control)
+ {
+ if (control == null)
+ {
+ return;
+ }
+
+ _currentScale = 1;
+ Vector3D currentOffset = default;
+
+ CompositionVisual? compositionVisual = null;
+
+ void InitComposition(Control visual)
+ {
+ if (compositionVisual != null)
+ {
+ return;
+ }
+
+ compositionVisual = ElementComposition.GetElementVisual(visual);
+ }
+
+ control.LayoutUpdated += (s, e) =>
+ {
+ InitComposition(control!);
+ if (compositionVisual != null)
+ {
+ compositionVisual.Scale = new(_currentScale, _currentScale, 1);
+
+ if (currentOffset == default)
+ {
+ currentOffset = compositionVisual.Offset;
+ }
+ }
+ };
+
+ control.AddHandler(InputElement.PinchEvent, (s, e) =>
+ {
+ InitComposition(control!);
+
+ if (compositionVisual != null)
+ {
+ var scale = _currentScale * (float)e.Scale;
+
+ if (scale <= 1)
+ {
+ scale = 1;
+ compositionVisual.Offset = default;
+ }
+
+ compositionVisual.Scale = new(scale, scale, 1);
+
+ e.Handled = true;
+ }
+ });
+
+ control.AddHandler(InputElement.PinchEndedEvent, (s, e) =>
+ {
+ InitComposition(control!);
+
+ if (compositionVisual != null)
+ {
+ _currentScale = compositionVisual.Scale.X;
+ }
+ });
+
+ control.AddHandler(InputElement.ScrollGestureEvent, (s, e) =>
+ {
+ InitComposition(control!);
+
+ if (compositionVisual != null && _currentScale != 1)
+ {
+ currentOffset += new Vector3D(e.Delta.X, e.Delta.Y, 0);
+
+ var currentSize = control.Bounds.Size * _currentScale;
+
+ currentOffset = new Vector3D(Math.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
+ (float)Math.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height),
+ 0);
+
+ compositionVisual.Offset = currentOffset * -1;
+
+ e.Handled = true;
+ }
+ });
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/Gestures/GesturePullPage.xaml b/samples/ControlCatalog/Pages/Gestures/GesturePullPage.xaml
new file mode 100644
index 0000000000..6fa77bafd7
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GesturePullPage.xaml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/Gestures/GesturePullPage.xaml.cs b/samples/ControlCatalog/Pages/Gestures/GesturePullPage.xaml.cs
new file mode 100644
index 0000000000..e375ce5b4a
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GesturePullPage.xaml.cs
@@ -0,0 +1,100 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.LogicalTree;
+using Avalonia.Rendering.Composition;
+
+namespace ControlCatalog.Pages
+{
+ public partial class GesturePullPage : UserControl
+ {
+ private bool _isInit;
+
+ public GesturePullPage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ if (_isInit)
+ {
+ return;
+ }
+
+ _isInit = true;
+
+ SetPullHandlers(TopPullZone, false);
+ SetPullHandlers(BottomPullZone, true);
+ SetPullHandlers(RightPullZone, true);
+ SetPullHandlers(LeftPullZone, false);
+ }
+
+ private void SetPullHandlers(Control control, bool inverse)
+ {
+ var ball = control.FindLogicalDescendantOfType();
+
+ Vector3D defaultOffset = default;
+
+ CompositionVisual? ballCompositionVisual = null;
+
+ if (ball != null)
+ {
+ InitComposition(ball);
+ }
+ else
+ {
+ return;
+ }
+
+ control.LayoutUpdated += (s, e) =>
+ {
+ InitComposition(ball!);
+ if (ballCompositionVisual != null)
+ {
+ defaultOffset = ballCompositionVisual.Offset;
+ }
+ };
+
+ control.AddHandler(InputElement.PullGestureEvent, (s, e) =>
+ {
+ InitComposition(ball!);
+ if (ballCompositionVisual != null)
+ {
+ ballCompositionVisual.Offset = defaultOffset + new Vector3D(e.Delta.X * 0.4f, e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
+ e.Handled = true;
+ }
+ });
+
+ control.AddHandler(InputElement.PullGestureEndedEvent, (s, e) =>
+ {
+ InitComposition(ball!);
+ if (ballCompositionVisual != null)
+ {
+ ballCompositionVisual.Offset = defaultOffset;
+ }
+ });
+
+ void InitComposition(Control control)
+ {
+ ballCompositionVisual = ElementComposition.GetElementVisual(ball);
+
+ if (ballCompositionVisual != null)
+ {
+ var offsetAnimation = ballCompositionVisual.Compositor.CreateVector3KeyFrameAnimation();
+ offsetAnimation.Target = "Offset";
+ offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
+ offsetAnimation.Duration = TimeSpan.FromMilliseconds(100);
+
+ var implicitAnimations = ballCompositionVisual.Compositor.CreateImplicitAnimationCollection();
+ implicitAnimations["Offset"] = offsetAnimation;
+
+ ballCompositionVisual.ImplicitAnimations = implicitAnimations;
+ }
+ }
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/Gestures/GestureSwipePage.xaml b/samples/ControlCatalog/Pages/Gestures/GestureSwipePage.xaml
new file mode 100644
index 0000000000..1ba905377d
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GestureSwipePage.xaml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/Gestures/GestureSwipePage.xaml.cs b/samples/ControlCatalog/Pages/Gestures/GestureSwipePage.xaml.cs
new file mode 100644
index 0000000000..92b5fafaca
--- /dev/null
+++ b/samples/ControlCatalog/Pages/Gestures/GestureSwipePage.xaml.cs
@@ -0,0 +1,125 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Controls.Primitives;
+using Avalonia.Rendering.Composition;
+
+namespace ControlCatalog.Pages
+{
+ public partial class GestureSwipePage : UserControl
+ {
+ private bool _isInit;
+ private CompositionVisual? _indicatorVisual;
+ private Vector3D _swipeDelta;
+
+ public GestureSwipePage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ if (_isInit)
+ {
+ return;
+ }
+
+ _isInit = true;
+
+ SwipePanel.AddHandler(InputElement.SwipeGestureEvent, OnSwipeGesture);
+ SwipePanel.AddHandler(InputElement.SwipeGestureEndedEvent, OnSwipeGestureEnded);
+ }
+
+ private void EnsureVisual()
+ {
+ if (_indicatorVisual != null)
+ {
+ return;
+ }
+
+ _indicatorVisual = ElementComposition.GetElementVisual(SwipeIndicator);
+
+ if (_indicatorVisual != null)
+ {
+ var offsetAnimation = _indicatorVisual.Compositor.CreateVector3KeyFrameAnimation();
+ offsetAnimation.Target = "Offset";
+ offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
+ offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
+
+ var implicitAnimations = _indicatorVisual.Compositor.CreateImplicitAnimationCollection();
+ implicitAnimations["Offset"] = offsetAnimation;
+
+ _indicatorVisual.ImplicitAnimations = implicitAnimations;
+ }
+ }
+
+ private void OnSwipeGesture(object? sender, SwipeGestureEventArgs e)
+ {
+ EnsureVisual();
+
+ if (_indicatorVisual != null)
+ {
+ _swipeDelta += new Vector3D(-e.Delta.X, -e.Delta.Y, 0);
+ _indicatorVisual.Offset += new Vector3D(-e.Delta.X, -e.Delta.Y, 0);
+ }
+
+ DirectionText.Text = $"Direction: {e.SwipeDirection}";
+ DeltaText.Text = $"Delta: {e.Delta.X:F1}, {e.Delta.Y:F1}";
+ VelocityText.Text = $"Velocity: {e.Velocity.X:F0}, {e.Velocity.Y:F0}";
+ SwipeDirectionLabel.Text = e.SwipeDirection.ToString();
+ SwipeDirectionLabel.Opacity = 1;
+
+ e.Handled = true;
+ }
+
+ private void OnSwipeGestureEnded(object? sender, SwipeGestureEndedEventArgs e)
+ {
+ if (_indicatorVisual != null)
+ {
+ _indicatorVisual.Offset -= _swipeDelta;
+ }
+
+ _swipeDelta = default;
+
+ VelocityText.Text = $"Velocity: {e.Velocity.X:F0}, {e.Velocity.Y:F0} (ended)";
+ SwipeDirectionLabel.Text = "Swipe here";
+ SwipeDirectionLabel.Opacity = 0.5;
+ }
+
+ private void OnDirectionChanged(object? sender, RoutedEventArgs e)
+ {
+ if (SwipeRecognizer == null)
+ {
+ return;
+ }
+
+ SwipeRecognizer.CanHorizontallySwipe = HorizontalCheck.IsChecked == true;
+ SwipeRecognizer.CanVerticallySwipe = VerticalCheck.IsChecked == true;
+ }
+
+ private void OnMouseEnabledChanged(object? sender, RoutedEventArgs e)
+ {
+ if (SwipeRecognizer == null)
+ {
+ return;
+ }
+
+ SwipeRecognizer.IsMouseEnabled = MouseCheck.IsChecked == true;
+ }
+
+ private void OnThresholdChanged(object? sender, RangeBaseValueChangedEventArgs e)
+ {
+ if (SwipeRecognizer == null || ThresholdText == null)
+ {
+ return;
+ }
+
+ SwipeRecognizer.Threshold = e.NewValue;
+ ThresholdText.Text = $"{e.NewValue:F0}";
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs
index beb0b2dccb..3ccdcaefa8 100644
--- a/samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/NavigationPage/LAvenirAppPage.xaml.cs
@@ -59,26 +59,6 @@ public partial class LAvenirAppPage : UserControl
_infoPanel.IsVisible = Bounds.Width >= 650;
}
- void ApplyRootNavigationBarAppearance()
- {
- if (_navPage == null)
- return;
-
- _navPage.Background = new SolidColorBrush(BgLight);
- _navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgLight);
- _navPage.Resources["NavigationBarForeground"] = new SolidColorBrush(TextDark);
- }
-
- void ApplyDetailNavigationBarAppearance()
- {
- if (_navPage == null)
- return;
-
- _navPage.Background = new SolidColorBrush(BgDark);
- _navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgDark);
- _navPage.Resources["NavigationBarForeground"] = Brushes.White;
- }
-
TabbedPage BuildMenuTabbedPage()
{
var tp = new TabbedPage
@@ -112,7 +92,6 @@ public partial class LAvenirAppPage : UserControl
VerticalAlignment = VerticalAlignment.Center,
TextAlignment = TextAlignment.Center,
};
- ApplyRootNavigationBarAppearance();
NavigationPage.SetTopCommandBar(tp, new Button
{
@@ -140,7 +119,7 @@ public partial class LAvenirAppPage : UserControl
Content = menuView,
Background = new SolidColorBrush(BgLight),
Header = "Menu",
- Icon = Geometry.Parse("M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z") },
};
var reservationsPage = new ContentPage
@@ -148,7 +127,7 @@ public partial class LAvenirAppPage : UserControl
Content = new LAvenirReservationsView(),
Background = new SolidColorBrush(BgLight),
Header = "Reservations",
- Icon = Geometry.Parse("M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z") },
};
var profilePage = new ContentPage
@@ -156,7 +135,7 @@ public partial class LAvenirAppPage : UserControl
Content = new LAvenirProfileView(),
Background = new SolidColorBrush(BgLight),
Header = "Profile",
- Icon = Geometry.Parse("M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z") },
};
tp.Pages = new ObservableCollection { menuPage, reservationsPage, profilePage };
@@ -165,8 +144,7 @@ public partial class LAvenirAppPage : UserControl
async void PushDishDetail(string name, string price, string description, string imageFile)
{
- if (_navPage == null)
- return;
+ if (_navPage == null) return;
var detail = new ContentPage
{
@@ -175,19 +153,22 @@ public partial class LAvenirAppPage : UserControl
Header = name,
};
NavigationPage.SetBottomCommandBar(detail, BuildFloatingBar(price));
- detail.Navigating += args =>
- {
- if (args.NavigationType == NavigationType.Pop)
- ApplyRootNavigationBarAppearance();
- return Task.CompletedTask;
+ _navPage.Background = new SolidColorBrush(BgDark);
+ _navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgDark);
+ _navPage.Resources["NavigationBarForeground"] = Brushes.White;
+
+ detail.NavigatedFrom += (_, _) =>
+ {
+ if (_navPage != null)
+ {
+ _navPage.Background = new SolidColorBrush(BgLight);
+ _navPage.Resources["NavigationBarBackground"] = new SolidColorBrush(BgLight);
+ _navPage.Resources["NavigationBarForeground"] = new SolidColorBrush(TextDark);
+ }
};
- ApplyDetailNavigationBarAppearance();
await _navPage.PushAsync(detail);
-
- if (!ReferenceEquals(_navPage.CurrentPage, detail))
- ApplyRootNavigationBarAppearance();
}
Border BuildFloatingBar(string price)
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/samples/ControlCatalog/Pages/NavigationPage/PulseAppPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/PulseAppPage.xaml.cs
index f50d40f151..33e89b9b65 100644
--- a/samples/ControlCatalog/Pages/NavigationPage/PulseAppPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/NavigationPage/PulseAppPage.xaml.cs
@@ -99,7 +99,7 @@ public partial class PulseAppPage : UserControl
Content = homeView,
Background = new SolidColorBrush(BgDashboard),
Header = "Home",
- Icon = Geometry.Parse("M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z") },
};
var workoutsPage = new ContentPage
@@ -107,7 +107,7 @@ public partial class PulseAppPage : UserControl
Content = new PulseWorkoutsView(),
Background = new SolidColorBrush(BgDashboard),
Header = "Workouts",
- Icon = Geometry.Parse("M20.57 14.86L22 13.43 20.57 12 17 15.57 8.43 7 12 3.43 10.57 2 9.14 3.43 7.71 2 5.57 4.14 4.14 2.71 2.71 4.14l1.43 1.43L2 7.71l1.43 1.43L2 10.57 3.43 12 7 8.43 15.57 17 12 20.57 13.43 22l1.43-1.43L16.29 22l2.14-2.14 1.43 1.43 1.43-1.43-1.43-1.43L22 16.29z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M20.57 14.86L22 13.43 20.57 12 17 15.57 8.43 7 12 3.43 10.57 2 9.14 3.43 7.71 2 5.57 4.14 4.14 2.71 2.71 4.14l1.43 1.43L2 7.71l1.43 1.43L2 10.57 3.43 12 7 8.43 15.57 17 12 20.57 13.43 22l1.43-1.43L16.29 22l2.14-2.14 1.43 1.43 1.43-1.43-1.43-1.43L22 16.29z") },
};
var profilePage = new ContentPage
@@ -115,7 +115,7 @@ public partial class PulseAppPage : UserControl
Content = new PulseProfileView(),
Background = new SolidColorBrush(BgDashboard),
Header = "Profile",
- Icon = Geometry.Parse("M12 2C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zM12 14c-5.523 0-10 3.582-10 8a1 1 0 001 1h18a1 1 0 001-1c0-4.418-4.477-8-10-8z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M12 2C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zM12 14c-5.523 0-10 3.582-10 8a1 1 0 001 1h18a1 1 0 001-1c0-4.418-4.477-8-10-8z") },
};
tp.Pages = new ObservableCollection { homePage, workoutsPage, profilePage };
diff --git a/samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml.cs b/samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml.cs
index 25091493ea..6e194cf1a5 100644
--- a/samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/NavigationPage/RetroGamingAppPage.xaml.cs
@@ -51,30 +51,11 @@ public partial class RetroGamingAppPage : UserControl
_infoPanel.IsVisible = Bounds.Width >= 650;
}
- void ApplyHomeNavigationBarAppearance()
- {
- if (_nav == null)
- return;
-
- _nav.Resources["NavigationBarBackground"] = new SolidColorBrush(SurfaceColor);
- _nav.Resources["NavigationBarForeground"] = new SolidColorBrush(CyanColor);
- }
-
- void ApplyDetailNavigationBarAppearance()
- {
- if (_nav == null)
- return;
-
- _nav.Resources["NavigationBarBackground"] = Brushes.Transparent;
- _nav.Resources["NavigationBarForeground"] = new SolidColorBrush(CyanColor);
- }
-
ContentPage BuildHomePage()
{
var page = new ContentPage { Background = new SolidColorBrush(BgColor) };
page.Header = BuildPixelArcadeLogo();
NavigationPage.SetTopCommandBar(page, BuildNavBarRight());
- ApplyHomeNavigationBarAppearance();
var panel = new Panel();
panel.Children.Add(BuildHomeTabbedPage());
@@ -193,7 +174,7 @@ public partial class RetroGamingAppPage : UserControl
var homeTab = new ContentPage
{
Header = "Home",
- Icon = Geometry.Parse("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z") },
Background = new SolidColorBrush(BgColor),
Content = homeView,
};
@@ -204,7 +185,7 @@ public partial class RetroGamingAppPage : UserControl
var gamesTab = new ContentPage
{
Header = "Games",
- Icon = Geometry.Parse("M7.97,16L5,19C4.67,19.3 4.23,19.5 3.75,19.5A1.75,1.75 0 0,1 2,17.75V17.5L3,10.12C3.21,7.81 5.14,6 7.5,6H16.5C18.86,6 20.79,7.81 21,10.12L22,17.5V17.75A1.75,1.75 0 0,1 20.25,19.5C19.77,19.5 19.33,19.3 19,19L16.03,16H7.97M7,9V11H5V13H7V15H9V13H11V11H9V9H7M14.5,12A1.5,1.5 0 0,0 13,13.5A1.5,1.5 0 0,0 14.5,15A1.5,1.5 0 0,0 16,13.5A1.5,1.5 0 0,0 14.5,12M17.5,9A1.5,1.5 0 0,0 16,10.5A1.5,1.5 0 0,0 17.5,12A1.5,1.5 0 0,0 19,10.5A1.5,1.5 0 0,0 17.5,9Z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M7.97,16L5,19C4.67,19.3 4.23,19.5 3.75,19.5A1.75,1.75 0 0,1 2,17.75V17.5L3,10.12C3.21,7.81 5.14,6 7.5,6H16.5C18.86,6 20.79,7.81 21,10.12L22,17.5V17.75A1.75,1.75 0 0,1 20.25,19.5C19.77,19.5 19.33,19.3 19,19L16.03,16H7.97M7,9V11H5V13H7V15H9V13H11V11H9V9H7M14.5,12A1.5,1.5 0 0,0 13,13.5A1.5,1.5 0 0,0 14.5,15A1.5,1.5 0 0,0 16,13.5A1.5,1.5 0 0,0 14.5,12M17.5,9A1.5,1.5 0 0,0 16,10.5A1.5,1.5 0 0,0 17.5,12A1.5,1.5 0 0,0 19,10.5A1.5,1.5 0 0,0 17.5,9Z") },
Background = new SolidColorBrush(BgColor),
Content = gamesView,
};
@@ -212,7 +193,7 @@ public partial class RetroGamingAppPage : UserControl
var favTab = new ContentPage
{
Header = "Favorites",
- Icon = Geometry.Parse("M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z") },
Background = new SolidColorBrush(BgColor),
Content = new RetroGamingFavoritesView(),
};
@@ -220,7 +201,7 @@ public partial class RetroGamingAppPage : UserControl
var profileTab = new ContentPage
{
Header = "Profile",
- Icon = Geometry.Parse("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"),
+ Icon = new PathIcon { Data = Geometry.Parse("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z") },
Background = new SolidColorBrush(BgColor),
Content = new RetroGamingProfileView(),
};
@@ -279,8 +260,7 @@ public partial class RetroGamingAppPage : UserControl
async void PushDetailPage(string gameTitle)
{
- if (_nav == null)
- return;
+ if (_nav == null) return;
var detailView = new RetroGamingDetailView(gameTitle);
@@ -291,13 +271,8 @@ public partial class RetroGamingAppPage : UserControl
};
NavigationPage.SetBarLayoutBehavior(page, BarLayoutBehavior.Overlay);
- page.Navigating += args =>
- {
- if (args.NavigationType == NavigationType.Pop)
- ApplyHomeNavigationBarAppearance();
-
- return Task.CompletedTask;
- };
+ page.NavigatedTo += (_, _) => { if (_nav != null) _nav.Resources["NavigationBarBackground"] = Brushes.Transparent; };
+ page.NavigatedFrom += (_, _) => { if (_nav != null) _nav.Resources["NavigationBarBackground"] = new SolidColorBrush(SurfaceColor); };
var cmdBar = new StackPanel
{
@@ -326,10 +301,6 @@ public partial class RetroGamingAppPage : UserControl
cmdBar.Children.Add(shareBtn);
NavigationPage.SetTopCommandBar(page, cmdBar);
- ApplyDetailNavigationBarAppearance();
await _nav.PushAsync(page);
-
- if (!ReferenceEquals(_nav.CurrentPage, page))
- ApplyHomeNavigationBarAppearance();
}
}
diff --git a/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonsPage.xaml b/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonThemesPage.xaml
similarity index 83%
rename from samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonsPage.xaml
rename to samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonThemesPage.xaml
index 8b9856424d..3e32f253f5 100644
--- a/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonsPage.xaml
+++ b/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonThemesPage.xaml
@@ -1,17 +1,17 @@
+ x:Class="ControlCatalog.Pages.PipsPagerCustomButtonThemesPage">
-
+ Text="Replace the default chevron navigation buttons with custom button themes using PreviousButtonTheme and NextButtonTheme." />
-
-
+
+
@@ -27,7 +27,7 @@
-
+
@@ -41,7 +41,7 @@
-
+
@@ -56,12 +56,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonThemesPage.xaml.cs b/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonThemesPage.xaml.cs
new file mode 100644
index 0000000000..26682b9d61
--- /dev/null
+++ b/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonThemesPage.xaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace ControlCatalog.Pages;
+
+public partial class PipsPagerCustomButtonThemesPage : UserControl
+{
+ public PipsPagerCustomButtonThemesPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonsPage.xaml.cs b/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonsPage.xaml.cs
deleted file mode 100644
index 4fc74995bc..0000000000
--- a/samples/ControlCatalog/Pages/PipsPager/PipsPagerCustomButtonsPage.xaml.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Avalonia.Controls;
-
-namespace ControlCatalog.Pages;
-
-public partial class PipsPagerCustomButtonsPage : UserControl
-{
- public PipsPagerCustomButtonsPage()
- {
- InitializeComponent();
- }
-}
diff --git a/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs b/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs
index 33cc6d0fdf..0559a265e4 100644
--- a/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/PipsPagerPage.xaml.cs
@@ -25,9 +25,9 @@ namespace ControlCatalog.Pages
("Appearance", "Custom Colors",
"Override pip indicator colors using resource keys for normal, selected, and hover states.",
() => new PipsPagerCustomColorsPage()),
- ("Appearance", "Custom Buttons",
- "Replace the default chevron navigation buttons with custom styled buttons.",
- () => new PipsPagerCustomButtonsPage()),
+ ("Appearance", "Custom Button Themes",
+ "Replace the default chevron navigation buttons with custom button themes.",
+ () => new PipsPagerCustomButtonThemesPage()),
("Appearance", "Custom Templates",
"Override pip item templates to create squares, pills, numbers, or any custom shape.",
() => new PipsPagerCustomTemplatesPage()),
diff --git a/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs b/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
index 57f8a138af..0e45b28a78 100644
--- a/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
+++ b/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
@@ -1,4 +1,6 @@
-using Avalonia;
+using System;
+using System.Linq;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
@@ -8,6 +10,8 @@ namespace ControlSamples
public class HamburgerMenu : TabControl
{
private SplitView? _splitView;
+ private TextBox? _searchBox;
+ private bool _initialized;
public static readonly StyledProperty PaneBackgroundProperty =
SplitView.PaneBackgroundProperty.AddOwner();
@@ -41,6 +45,68 @@ namespace ControlSamples
base.OnApplyTemplate(e);
_splitView = e.NameScope.Find("PART_NavigationPane");
+ _searchBox = e.NameScope.Find("PART_SearchBox");
+
+ if (_searchBox is not null)
+ {
+ _searchBox.TextChanged += OnSearchTextChanged;
+ }
+ }
+
+ protected override void OnLoaded(Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+
+ if (!_initialized)
+ {
+ _initialized = true;
+ SortItems();
+ }
+ }
+
+ private void SortItems()
+ {
+ var items = Items.OfType().ToList();
+ var sorted = items.OrderBy(t => t.Header?.ToString() ?? "", StringComparer.OrdinalIgnoreCase).ToList();
+
+ // Only reorder if needed
+ bool needsSort = false;
+ for (int i = 0; i < items.Count; i++)
+ {
+ if (!ReferenceEquals(items[i], sorted[i]))
+ {
+ needsSort = true;
+ break;
+ }
+ }
+
+ if (needsSort)
+ {
+ Items.Clear();
+ foreach (var item in sorted)
+ {
+ Items.Add(item);
+ }
+ }
+ }
+
+ private void OnSearchTextChanged(object? sender, TextChangedEventArgs e)
+ {
+ var searchText = _searchBox?.Text;
+ var hasFilter = !string.IsNullOrWhiteSpace(searchText);
+
+ foreach (var item in Items.OfType())
+ {
+ if (hasFilter)
+ {
+ var header = item.Header?.ToString() ?? "";
+ item.IsVisible = header.Contains(searchText!, StringComparison.OrdinalIgnoreCase);
+ }
+ else
+ {
+ item.IsVisible = true;
+ }
+ }
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
diff --git a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
index 49972bdc64..d9b3e3c73f 100644
--- a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
+++ b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
@@ -175,10 +175,21 @@
OpenPaneLength="{StaticResource PaneExpandWidth}"
PaneBackground="Transparent">
-
+
+
+
+
+
+
@@ -195,7 +206,7 @@