diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 838d2bd70b..602939f18b 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -1,4 +1,4 @@
-
+
@@ -109,6 +109,42 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Input.IKeyboardNavigationHandler
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.KeyboardNavigationHandler
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.TextInput.ITextInputMethodRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.IEmbeddedLayoutRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.ILayoutRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.LayoutManager
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Media.Fonts.FontFamilyLoader
@@ -151,6 +187,24 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Rendering.IHitTester
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderer
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Styling.IStyleable
@@ -391,6 +445,42 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Input.IKeyboardNavigationHandler
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.KeyboardNavigationHandler
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Input.TextInput.ITextInputMethodRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.IEmbeddedLayoutRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.ILayoutRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Layout.LayoutManager
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Media.Fonts.FontFamilyLoader
@@ -433,6 +523,24 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Rendering.IHitTester
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderer
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Rendering.IRenderRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Styling.IStyleable
@@ -685,6 +793,42 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_KeyboardNavigationHandler
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PlatformSettings
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PointerOverElement
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_ShowAccessKeys
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_PointerOverElement(Avalonia.Input.IInputElement)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_ShowAccessKeys(System.Boolean)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)
@@ -973,12 +1117,30 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.#ctor(Avalonia.Rendering.IRenderRoot,Avalonia.Rect)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.get_RenderRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}})
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Visual.get_VisualRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)
@@ -1003,6 +1165,24 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.VisualTree.VisualExtensions.GetVisualRoot(Avalonia.Visual)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.#ctor(Avalonia.Visual,Avalonia.Rendering.IRenderRoot)
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.get_Root
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0002
F:Avalonia.Controls.ContextMenu.PlacementModeProperty
@@ -1063,6 +1243,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.TopLevel.PointerOverElementProperty
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.AppBuilder.get_LifetimeOverride
@@ -1333,6 +1519,30 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.#ctor(Avalonia.Platform.ITopLevelImpl,Avalonia.IAvaloniaDependencyResolver)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.get_PlatformSettings
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StartRendering
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StopRendering
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.TreeView.get_ItemContainerGenerator
@@ -1603,6 +1813,42 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_KeyboardNavigationHandler
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PlatformSettings
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_PointerOverElement
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.get_ShowAccessKeys
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_PointerOverElement(Avalonia.Input.IInputElement)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Input.IInputRoot.set_ShowAccessKeys(System.Boolean)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)
@@ -1891,12 +2137,30 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.#ctor(Avalonia.Rendering.IRenderRoot,Avalonia.Rect)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Rendering.SceneInvalidatedEventArgs.get_RenderRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}})
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Visual.get_VisualRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)
@@ -1921,6 +2185,24 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.VisualTree.VisualExtensions.GetVisualRoot(Avalonia.Visual)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.#ctor(Avalonia.Visual,Avalonia.Rendering.IRenderRoot)
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.VisualTreeAttachmentEventArgs.get_Root
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0002
F:Avalonia.Controls.ContextMenu.PlacementModeProperty
@@ -1981,6 +2263,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ F:Avalonia.Controls.TopLevel.PointerOverElementProperty
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.AppBuilder.get_LifetimeOverride
@@ -2251,6 +2539,30 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.#ctor(Avalonia.Platform.ITopLevelImpl,Avalonia.IAvaloniaDependencyResolver)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.get_PlatformSettings
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StartRendering
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0002
+ M:Avalonia.Controls.TopLevel.StopRendering
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0002
M:Avalonia.Controls.TreeView.get_ItemContainerGenerator
@@ -2839,6 +3151,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
+ CP0008
+ T:Avalonia.Input.IInputRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll
+ current/Avalonia/lib/net10.0/Avalonia.Base.dll
+
CP0008
T:Avalonia.Media.ImmediateDrawingContext
@@ -2881,6 +3199,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Embedding.EmbeddableControlRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase
@@ -2893,6 +3217,36 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Primitives.OverlayPopupHost
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Primitives.PopupRoot
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.TopLevel
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Window
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.WindowBase
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Platform.IPopupImpl
@@ -2917,6 +3271,12 @@
baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Dialogs.AboutAvaloniaDialog
+ baseline/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net10.0/Avalonia.Dialogs.dll
+
CP0008
T:Avalonia.Metal.IMetalDevice
@@ -2959,6 +3319,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
+ CP0008
+ T:Avalonia.Input.IInputRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll
+ current/Avalonia/lib/net8.0/Avalonia.Base.dll
+
CP0008
T:Avalonia.Media.ImmediateDrawingContext
@@ -3001,6 +3367,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Embedding.EmbeddableControlRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase
@@ -3013,6 +3385,36 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Controls.Primitives.OverlayPopupHost
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Primitives.PopupRoot
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.TopLevel
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.Window
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+
+ CP0008
+ T:Avalonia.Controls.WindowBase
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
CP0008
T:Avalonia.Platform.IPopupImpl
@@ -3037,6 +3439,12 @@
baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
+ CP0008
+ T:Avalonia.Dialogs.AboutAvaloniaDialog
+ baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+ current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll
+
CP0008
T:Avalonia.Metal.IMetalDevice
@@ -3241,4 +3649,4 @@
baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll
-
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index ae8be8cfc6..a6274b56e7 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -45,7 +45,7 @@ namespace ControlCatalog
private void Decorations_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
- if (VisualRoot is Window window && e.AddedItems.Count > 0 && e.AddedItems[0] is SystemDecorations systemDecorations)
+ if (TopLevel.GetTopLevel(this) is Window window && e.AddedItems.Count > 0 && e.AddedItems[0] is SystemDecorations systemDecorations)
{
window.SystemDecorations = systemDecorations;
}
@@ -78,7 +78,7 @@ namespace ControlCatalog
{
base.OnAttachedToVisualTree(e);
- if (VisualRoot is Window window)
+ if (TopLevel.GetTopLevel(this) is Window window)
Decorations.SelectedIndex = (int)window.SystemDecorations;
var insets = TopLevel.GetTopLevel(this)!.InsetsManager;
diff --git a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
index f041f32b10..03156f6963 100644
--- a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
@@ -49,7 +49,7 @@ namespace ControlCatalog.ViewModels
public async Task Open()
{
- var window = View?.GetVisualRoot() as Window;
+ var window = TopLevel.GetTopLevel(View) as Window;
if (window == null)
return;
diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
index df62ba04cb..f6b406d0d9 100644
--- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
@@ -69,7 +69,7 @@ namespace ControlCatalog.ViewModels
public async Task Open()
{
- var window = View?.GetVisualRoot() as Window;
+ var window = TopLevel.GetTopLevel(View) as Window;
if (window == null)
return;
var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
diff --git a/samples/GpuInterop/DrawingSurfaceDemoBase.cs b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
index b076f5b489..10d105bca2 100644
--- a/samples/GpuInterop/DrawingSurfaceDemoBase.cs
+++ b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
@@ -71,12 +71,12 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
void UpdateFrame()
{
_updateQueued = false;
- var root = this.GetVisualRoot();
- if (root == null)
+ var source = this.GetPresentationSource();
+ if (source == null)
return;
_visual!.Size = new (Bounds.Width, Bounds.Height);
- var size = PixelSize.FromSize(Bounds.Size, root.RenderScaling);
+ var size = PixelSize.FromSize(Bounds.Size, source.RenderScaling);
RenderFrame(size);
if (SupportsDisco && Disco > 0)
QueueNextFrame();
diff --git a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs b/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
index 39e1574901..a93803db09 100644
--- a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
+++ b/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
@@ -157,8 +157,7 @@ namespace Avalonia.Android.Platform.Input
}
case ImeAction.Next:
{
- FocusManager.GetFocusManager(_toplevel.InputRoot)?
- .TryMoveFocus(NavigationDirection.Next);
+ ((FocusManager?)_toplevel.InputRoot?.FocusManager)?.TryMoveFocus(NavigationDirection.Next);
break;
}
}
diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs
index 758e28e07f..97d08b5620 100644
--- a/src/Avalonia.Base/Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
@@ -30,6 +31,12 @@ namespace Avalonia.Input
RoutingStrategies.Bubble,
typeof(AccessKeyHandler));
+ ///
+ /// Defines the ShowAccessKey attached property.
+ ///
+ public static readonly AttachedProperty ShowAccessKeyProperty =
+ AvaloniaProperty.RegisterAttached("ShowAccessKey", inherits: true);
+
///
/// The registered access keys.
///
@@ -40,7 +47,7 @@ namespace Avalonia.Input
///
/// The window to which the handler belongs.
///
- private IInputRoot? _owner;
+ private InputElement? _owner;
///
/// Whether access keys are currently being shown;
@@ -96,7 +103,7 @@ namespace Avalonia.Input
///
/// This method can only be called once, typically by the owner itself on creation.
///
- public void SetOwner(IInputRoot owner)
+ public void SetOwner(InputElement owner)
{
if (_owner != null)
{
@@ -113,7 +120,7 @@ namespace Avalonia.Input
OnSetOwner(owner);
}
- protected virtual void OnSetOwner(IInputRoot owner)
+ protected virtual void OnSetOwner(InputElement owner)
{
}
@@ -159,6 +166,9 @@ namespace Avalonia.Input
}
}
+ static void SetShowAccessKeys(AvaloniaObject target, bool value) =>
+ target.SetValue(ShowAccessKeyProperty, value);
+
///
/// Called when a key is pressed in the owner window.
///
@@ -188,7 +198,7 @@ namespace Avalonia.Input
// When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File").
- _owner!.ShowAccessKeys = _showingAccessKeys = isFocusWithinOwner;
+ SetShowAccessKeys(_owner!, _showingAccessKeys = isFocusWithinOwner);
}
else
{
@@ -265,7 +275,7 @@ namespace Avalonia.Input
{
if (_showingAccessKeys)
{
- _owner!.ShowAccessKeys = false;
+ SetShowAccessKeys(_owner!, false);
}
}
@@ -275,12 +285,12 @@ namespace Avalonia.Input
private void CloseMenu()
{
MainMenu!.Close();
- _owner!.ShowAccessKeys = _showingAccessKeys = false;
+ SetShowAccessKeys(_owner!, _showingAccessKeys = false);
}
private void MainMenuClosed(object? sender, EventArgs e)
{
- _owner!.ShowAccessKeys = false;
+ SetShowAccessKeys(_owner!, false);
}
///
@@ -444,7 +454,7 @@ namespace Avalonia.Input
///
/// The owner to check.
/// If focused element is decendant of owner true, otherwise false.
- private static bool IsFocusWithinOwner(IInputRoot owner)
+ private static bool IsFocusWithinOwner(IInputElement owner)
{
var focusedElement = KeyboardDevice.Instance?.FocusedElement;
if (focusedElement is not InputElement inputElement)
diff --git a/src/Avalonia.Base/Input/DragDropDevice.cs b/src/Avalonia.Base/Input/DragDropDevice.cs
index 8f3b542e8d..21321510e6 100644
--- a/src/Avalonia.Base/Input/DragDropDevice.cs
+++ b/src/Avalonia.Base/Input/DragDropDevice.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Input
private static Interactive? GetTarget(IInputRoot root, Point local)
{
- var hit = root.InputHitTest(local) as Visual;
+ var hit = root.RootElement?.InputHitTest(local) as Visual;
var target = hit?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target))
return target;
@@ -35,7 +35,7 @@ namespace Avalonia.Input
if (target == null)
return DragDropEffects.None;
- var p = ((Visual)inputRoot).TranslatePoint(point, target);
+ var p = (inputRoot.RootElement).TranslatePoint(point, target);
if (!p.HasValue)
return DragDropEffects.None;
diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs
index ed741a36ae..675fa9952f 100644
--- a/src/Avalonia.Base/Input/FocusManager.cs
+++ b/src/Avalonia.Base/Input/FocusManager.cs
@@ -51,6 +51,11 @@ namespace Avalonia.Input
{
_contentRoot = contentRoot;
}
+
+ internal void SetContentRoot(IInputElement? contentRoot)
+ {
+ _contentRoot = contentRoot;
+ }
private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
@@ -120,7 +125,7 @@ namespace Avalonia.Input
scope.ClearValue(FocusedElementProperty);
}
- if (Current == removedElement)
+ if (Current == removedElement)
Focus(null);
}
@@ -163,9 +168,10 @@ namespace Avalonia.Input
///
internal static FocusManager? GetFocusManager(IInputElement? element)
{
+
// Element might not be a visual, and not attached to the root.
// But IFocusManager is always expected to be a FocusManager.
- return (FocusManager?)((element as Visual)?.VisualRoot as IInputRoot)?.FocusManager
+ return (FocusManager?)(element as Visual)?.GetInputRoot()?.FocusManager
// In our unit tests some elements might not have a root. Remove when we migrate to headless tests.
?? (FocusManager?)AvaloniaLocator.Current.GetService();
}
diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs
index 3298af3a0f..4929cd0ea6 100644
--- a/src/Avalonia.Base/Input/Gestures.cs
+++ b/src/Avalonia.Base/Input/Gestures.cs
@@ -257,7 +257,7 @@ namespace Avalonia.Input
s_lastPressPoint = e.GetPosition((Visual)source);
s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token;
- var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings;
+ var settings = visual.GetPlatformSettings();
if (settings != null)
{
@@ -298,7 +298,7 @@ namespace Avalonia.Input
source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
- var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
+ var settings = i.GetPlatformSettings();
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
@@ -340,7 +340,6 @@ namespace Avalonia.Input
if (e.Pointer == s_gestureState?.Pointer && source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
- var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
var holdSize = new Size(4, 4);
var holdRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(holdSize.Width, holdSize.Height));
diff --git a/src/Avalonia.Base/Input/IAccessKeyHandler.cs b/src/Avalonia.Base/Input/IAccessKeyHandler.cs
index 418fa61f05..44dda96eb1 100644
--- a/src/Avalonia.Base/Input/IAccessKeyHandler.cs
+++ b/src/Avalonia.Base/Input/IAccessKeyHandler.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Input
///
/// This method can only be called once, typically by the owner itself on creation.
///
- void SetOwner(IInputRoot owner);
+ void SetOwner(InputElement owner);
///
/// Registers an input element to be associated with an access key.
diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs
index c1c5968ebe..c98a7a4e57 100644
--- a/src/Avalonia.Base/Input/IInputRoot.cs
+++ b/src/Avalonia.Base/Input/IInputRoot.cs
@@ -1,3 +1,4 @@
+using Avalonia.Input.TextInput;
using Avalonia.Metadata;
using Avalonia.Platform;
@@ -6,38 +7,24 @@ namespace Avalonia.Input
///
/// Defines the interface for top-level input elements.
///
- [NotClientImplementable]
- public interface IInputRoot : IInputElement
+ [PrivateApi]
+ public interface IInputRoot
{
- ///
- /// Gets or sets the keyboard navigation handler.
- ///
- IKeyboardNavigationHandler? KeyboardNavigationHandler { get; }
-
///
/// Gets focus manager of the root.
///
///
/// Focus manager can be null only if window wasn't initialized yet.
///
- IFocusManager? FocusManager { get; }
-
- ///
- /// Represents a contract for accessing top-level platform-specific settings.
- ///
- ///
- /// PlatformSettings can be null only if window wasn't initialized yet.
- ///
- IPlatformSettings? PlatformSettings { get; }
+ public IFocusManager? FocusManager { get; }
///
/// Gets or sets the input element that the pointer is currently over.
///
- IInputElement? PointerOverElement { get; set; }
-
- ///
- /// Gets or sets a value indicating whether access keys are shown in the window.
- ///
- bool ShowAccessKeys { get; set; }
+ internal IInputElement? PointerOverElement { get; set; }
+
+ internal ITextInputMethodImpl? InputMethod { get; }
+
+ internal InputElement RootElement { get; }
}
}
diff --git a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
index e82bb5d216..5e5cac0c0b 100644
--- a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
+++ b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Input
/// Defines the interface for classes that handle keyboard navigation for a window.
///
[Unstable]
- public interface IKeyboardNavigationHandler
+ internal interface IKeyboardNavigationHandler
{
///
/// Sets the owner of the keyboard navigation handler.
@@ -16,7 +16,7 @@ namespace Avalonia.Input
/// This method can only be called once, typically by the owner itself on creation.
///
[PrivateApi]
- void SetOwner(IInputRoot owner);
+ void SetOwner(InputElement owner);
///
/// Moves the focus in the specified direction.
diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs
index d15abfbd3f..f929dcbe05 100644
--- a/src/Avalonia.Base/Input/InputElement.cs
+++ b/src/Avalonia.Base/Input/InputElement.cs
@@ -583,7 +583,9 @@ namespace Avalonia.Input
if (IsFocused)
{
- FocusManager.GetFocusManager(e.Root as IInputElement)?.ClearFocusOnElementRemoved(this, e.Parent);
+ var root = e.AttachmentPoint ?? e.RootVisual;
+ ((FocusManager?)e.PresentationSource.InputRoot.FocusManager)
+ ?.ClearFocusOnElementRemoved(this, root);
}
IsKeyboardFocusWithin = false;
diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs
index 3971ef9364..7711592557 100644
--- a/src/Avalonia.Base/Input/KeyboardDevice.cs
+++ b/src/Avalonia.Base/Input/KeyboardDevice.cs
@@ -191,15 +191,15 @@ namespace Avalonia.Input
// Clear keyboard focus from currently focused element
if (FocusedElement != null &&
(!((Visual)FocusedElement).IsAttachedToVisualTree ||
- _focusedRoot != ((Visual?)element)?.VisualRoot as IInputRoot) &&
+ _focusedRoot != ((Visual?)element)?.GetInputRoot()) &&
_focusedRoot != null)
{
- ClearChildrenFocusWithin(_focusedRoot, true);
+ ClearChildrenFocusWithin(_focusedRoot.RootElement, true);
}
SetIsFocusWithin(FocusedElement, element);
_focusedElement = element;
- _focusedRoot = ((Visual?)_focusedElement)?.VisualRoot as IInputRoot;
+ _focusedRoot = (_focusedElement as Visual)?.GetInputRoot();
interactive?.RaiseEvent(new RoutedEventArgs(InputElement.LostFocusEvent));
@@ -225,7 +225,7 @@ namespace Avalonia.Input
if(e.Handled)
return;
- var element = FocusedElement ?? e.Root;
+ var element = FocusedElement ?? e.Root.RootElement;
if (e is RawKeyEventArgs keyInput)
{
diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
index e5e7eb0699..88a293f6bb 100644
--- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
+++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
@@ -10,13 +10,12 @@ namespace Avalonia.Input
///
/// Handles keyboard navigation for a window.
///
- [Unstable]
- public sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
+ internal sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
{
///
/// The window to which the handler belongs.
///
- private IInputRoot? _owner;
+ private InputElement? _owner;
///
/// Sets the owner of the keyboard navigation handler.
@@ -26,7 +25,7 @@ namespace Avalonia.Input
/// This method can only be called once, typically by the owner itself on creation.
///
[PrivateApi]
- public void SetOwner(IInputRoot owner)
+ public void SetOwner(InputElement owner)
{
if (_owner != null)
{
@@ -56,7 +55,7 @@ namespace Avalonia.Input
private static IInputElement? GetNextPrivate(
IInputElement? element,
- IInputRoot? owner,
+ InputElement? owner,
NavigationDirection direction,
KeyDeviceType? keyDeviceType)
{
diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs
index 62105c7deb..d9766c1707 100644
--- a/src/Avalonia.Base/Input/MouseDevice.cs
+++ b/src/Avalonia.Base/Input/MouseDevice.cs
@@ -135,20 +135,20 @@ namespace Avalonia.Input
return new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind());
}
- private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
+ private bool MouseDown(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var source = _pointer.Captured ?? root.InputHitTest(p);
+ var source = _pointer.Captured ?? root.RootElement.InputHitTest(p);
if (source != null)
{
_pointer.Capture(source, CaptureSource.Implicit);
- var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ var settings = (source as Interactive)?.GetPlatformSettings();
if (settings is not null)
{
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds;
@@ -166,7 +166,7 @@ namespace Avalonia.Input
}
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
- var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
+ var e = new PointerPressedEventArgs(source, _pointer, root.RootElement, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
}
@@ -185,7 +185,7 @@ namespace Avalonia.Input
if (source is object)
{
- var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root.RootElement,
p, timestamp, properties, inputModifiers, intermediatePoints);
if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
@@ -209,7 +209,7 @@ namespace Avalonia.Input
if (source is not null)
{
- var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
+ var e = new PointerReleasedEventArgs(source, _pointer, root.RootElement, p, timestamp, props, inputModifiers,
_lastMouseDownButton);
try
@@ -244,7 +244,7 @@ namespace Avalonia.Input
if (source is not null)
{
- var e = new PointerWheelEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ var e = new PointerWheelEventArgs(source, _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@@ -264,7 +264,7 @@ namespace Avalonia.Input
if (source != null)
{
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureMagnifyEvent, source,
- _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@@ -284,7 +284,7 @@ namespace Avalonia.Input
if (source != null)
{
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureRotateEvent, source,
- _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@@ -304,7 +304,7 @@ namespace Avalonia.Input
if (source != null)
{
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureSwipeEvent, source,
- _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
+ _pointer, root.RootElement, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
diff --git a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
index 0f529142ca..ed93b86475 100644
--- a/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
+++ b/src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
@@ -105,12 +105,11 @@ public partial class XYFocus
private static bool IsOccluded(InputElement element, Rect elementBounds)
{
- // if (element is CHyperlink hyperlink)
- // {
- // element = hyperlink.GetContainingFrameworkElement();
- // }
-
- var root = (InputElement)element.GetVisualRoot()!;
+ // TODO: The check for bounds is no longer correct
+
+ var root = (InputElement?)element.VisualRoot;
+ if (root == null)
+ return true;
// Check if the element is within the visible area of the window
var visibleBounds = new Rect(0, 0, root.Bounds.Width, root.Bounds.Height);
diff --git a/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs b/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs
index 929d92a650..7aab6d8b65 100644
--- a/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs
+++ b/src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs
@@ -117,7 +117,9 @@ public partial class XYFocus
{
if (element == null) return null;
- var root = (InputElement)element.GetVisualRoot()!;
+ var root = (InputElement?)element.VisualRoot;
+ if (root == null)
+ return null;
var isRightToLeft = element.FlowDirection == FlowDirection.RightToLeft;
var mode = GetStrategy(element, direction, xyFocusOptions.NavigationStrategyOverride);
diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs
index 3f168f66b0..b3301ce612 100644
--- a/src/Avalonia.Base/Input/PenDevice.cs
+++ b/src/Avalonia.Base/Input/PenDevice.cs
@@ -97,7 +97,7 @@ namespace Avalonia.Input
}
private bool PenDown(Pointer pointer, ulong timestamp,
- IInputElement root, Point p, PointerPointProperties properties,
+ IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
var source = pointer.Captured ?? hitTest;
@@ -105,7 +105,7 @@ namespace Avalonia.Input
if (source != null)
{
pointer.Capture(source);
- var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ var settings = (source as Interactive)?.GetPlatformSettings();
if (settings is not null)
{
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds;
@@ -123,7 +123,7 @@ namespace Avalonia.Input
}
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
- var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
+ var e = new PointerPressedEventArgs(source, pointer, root.RootElement, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
}
@@ -140,7 +140,7 @@ namespace Avalonia.Input
if (source is not null)
{
- var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, root.RootElement,
p, timestamp, properties, inputModifiers, intermediatePoints);
if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
@@ -154,14 +154,14 @@ namespace Avalonia.Input
}
private bool PenUp(Pointer pointer, ulong timestamp,
- IInputElement root, Point p, PointerPointProperties properties,
+ IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
-
+
if (source is not null)
{
- var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers,
+ var e = new PointerReleasedEventArgs(source, pointer, root.RootElement, p, timestamp, properties, inputModifiers,
_lastMouseDownButton);
try
diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs
index ebb98dd0bc..7de3dbe713 100644
--- a/src/Avalonia.Base/Input/Pointer.cs
+++ b/src/Avalonia.Base/Input/Pointer.cs
@@ -108,14 +108,14 @@ namespace Avalonia.Input
}
}
- static IInputElement? GetNextCapture(Visual parent)
+ static IInputElement? GetNextCapture(Visual? parent)
{
return parent as IInputElement ?? parent.FindAncestorOfType();
}
private void OnCaptureDetached(object? sender, VisualTreeAttachmentEventArgs e)
{
- Capture(GetNextCapture(e.Parent));
+ Capture(GetNextCapture(e.AttachmentPoint));
}
diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
index ed2b4c8669..beb171467d 100644
--- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
+++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
@@ -38,7 +38,7 @@ namespace Avalonia.Input
// occurred.
//
// Solve this by updating the last known pointer position when a drag event occurs.
- _lastKnownPosition = ((Visual)_inputRoot).PointToScreen(dragArgs.Location);
+ _lastKnownPosition = _inputRoot.RootElement.PointToScreen(dragArgs.Location);
}
else if (value is RawPointerEventArgs args
@@ -64,7 +64,7 @@ namespace Avalonia.Input
args.InputModifiers.ToKeyModifiers());
}
}
- else if (args.Type is RawPointerEventType.TouchBegin or RawPointerEventType.TouchUpdate && args.Root is Visual visual)
+ else if (args.Type is RawPointerEventType.TouchBegin or RawPointerEventType.TouchUpdate && args.Root.RootElement is {} visual)
{
_lastKnownPosition = visual.PointToScreen(args.Position);
}
@@ -99,12 +99,12 @@ namespace Avalonia.Input
if (dirtyRect.Contains(clientPoint))
{
var element = GetEffectivePointerOverElement(
- _inputRoot.InputHitTest(clientPoint),
+ _inputRoot.RootElement.InputHitTest(clientPoint),
pointer.Captured);
SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
}
- else if (!((Visual)_inputRoot).Bounds.Contains(clientPoint))
+ else if (!_inputRoot.RootElement.Bounds.Contains(clientPoint))
{
ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
}
@@ -140,16 +140,16 @@ namespace Avalonia.Input
// so GetPosition won't return invalid values.
#pragma warning disable CS0618
var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer,
- position.HasValue ? root as Visual : null, position.HasValue ? position.Value : default,
+ position.HasValue ? root.RootElement : null, position.HasValue ? position.Value : default,
timestamp, properties, inputModifiers);
#pragma warning restore CS0618
if (element is Visual v && !v.IsAttachedToVisualTree)
{
// element has been removed from visual tree so do top down cleanup
- if (root.IsPointerOver)
+ if (root.RootElement.IsPointerOver)
{
- ClearChildrenPointerOver(e, root, true);
+ ClearChildrenPointerOver(e, root.RootElement, true);
}
}
while (element != null)
@@ -191,7 +191,7 @@ namespace Avalonia.Input
ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers)
{
var pointerOverElement = root.PointerOverElement;
- var screenPosition = ((Visual)root).PointToScreen(position);
+ var screenPosition = (root.RootElement).PointToScreen(position);
_lastKnownPosition = screenPosition;
if (element != pointerOverElement)
@@ -229,7 +229,7 @@ namespace Avalonia.Input
el = root.PointerOverElement;
#pragma warning disable CS0618
- var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, (Visual)root, position,
+ var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, root.RootElement, position,
timestamp, properties, inputModifiers);
#pragma warning restore CS0618
if (el is Visual v && branch != null && !v.IsAttachedToVisualTree)
@@ -265,7 +265,7 @@ namespace Avalonia.Input
private static Point PointToClient(IInputRoot root, PixelPoint p)
{
- return ((Visual)root).PointToClient(p);
+ return (root.RootElement).PointToClient(p);
}
}
}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
index 2969b2e60d..471bbaa081 100644
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
+++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
@@ -10,10 +10,4 @@ namespace Avalonia.Input.TextInput
void SetOptions(TextInputOptions options);
void Reset();
}
-
- [NotClientImplementable]
- public interface ITextInputMethodRoot : IInputRoot
- {
- ITextInputMethodImpl? InputMethod { get; }
- }
}
diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
index 005a015644..930e827874 100644
--- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
+++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
@@ -132,7 +132,7 @@ namespace Avalonia.Input.TextInput
InputMethod.AddTextInputMethodClientRequeryRequestedHandler(_visualRoot,
TextInputMethodClientRequeryRequested);
- var inputMethod = ((element as Visual)?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
+ var inputMethod = ((element as Visual)?.GetInputRoot())?.InputMethod;
if (_im != inputMethod)
{
diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs
index 8e662ea1b9..27381bbe00 100644
--- a/src/Avalonia.Base/Input/TouchDevice.cs
+++ b/src/Avalonia.Base/Input/TouchDevice.cs
@@ -51,7 +51,7 @@ namespace Avalonia.Input
pointer.Capture(hit);
}
- var target = pointer.Captured ?? args.InputHitTestResult.firstEnabledAncestor ?? args.Root;
+ var target = pointer.Captured ?? args.InputHitTestResult.firstEnabledAncestor ?? args.Root.RootElement;
var gestureTarget = pointer.CapturedGestureRecognizer?.Target;
var updateKind = args.Type.ToUpdateKind();
var keyModifier = args.InputModifiers.ToKeyModifiers();
@@ -66,7 +66,7 @@ namespace Avalonia.Input
}
else
{
- var settings = ((IInputRoot?)(target as Interactive)?.GetVisualRoot())?.PlatformSettings;
+ var settings = (target as Interactive)?.GetPlatformSettings();
if (settings is not null)
{
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
@@ -86,7 +86,7 @@ namespace Avalonia.Input
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
- (Visual)args.Root, args.Position, ev.Timestamp,
+ args.Root.RootElement, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind, args.Point),
keyModifier, _clickCount));
}
@@ -98,7 +98,7 @@ namespace Avalonia.Input
{
target = gestureTarget ?? target;
var e = new PointerReleasedEventArgs(target, pointer,
- (Visual)args.Root, args.Position, ev.Timestamp,
+ args.Root.RootElement, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind, args.Point),
keyModifier, MouseButton.Left);
if (gestureTarget != null)
@@ -127,7 +127,7 @@ namespace Avalonia.Input
if (args.Type == RawPointerEventType.TouchUpdate)
{
target = gestureTarget ?? target;
- var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, (Visual)args.Root,
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, args.Root.RootElement,
args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind, args.Point),
keyModifier, args.IntermediatePoints);
diff --git a/src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs b/src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs
deleted file mode 100644
index 24f0ccd82e..0000000000
--- a/src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Avalonia.Layout
-{
- ///
- /// A special layout root with enforced size for Arrange pass
- ///
- public interface IEmbeddedLayoutRoot : ILayoutRoot
- {
- Size AllocatedSize { get; }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs
index 5035d3a48d..560d0aab00 100644
--- a/src/Avalonia.Base/Layout/ILayoutManager.cs
+++ b/src/Avalonia.Base/Layout/ILayoutManager.cs
@@ -6,7 +6,6 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- [PrivateApi]
public interface ILayoutManager : IDisposable
{
///
diff --git a/src/Avalonia.Base/Layout/ILayoutRoot.cs b/src/Avalonia.Base/Layout/ILayoutRoot.cs
index efea5bfed8..8624194c42 100644
--- a/src/Avalonia.Base/Layout/ILayoutRoot.cs
+++ b/src/Avalonia.Base/Layout/ILayoutRoot.cs
@@ -5,22 +5,18 @@ namespace Avalonia.Layout
///
/// Defines the root of a layoutable tree.
///
- [NotClientImplementable]
- public interface ILayoutRoot
+ internal interface ILayoutRoot
{
- ///
- /// The size available to lay out the controls.
- ///
- Size ClientSize { get; }
-
///
/// The scaling factor to use in layout.
///
- double LayoutScaling { get; }
+ public double LayoutScaling { get; }
///
/// Associated instance of layout manager
///
- internal ILayoutManager LayoutManager { get; }
+ public ILayoutManager LayoutManager { get; }
+
+ public Layoutable RootVisual { get; }
}
}
diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs
index a342c654f9..84f1fc4bcc 100644
--- a/src/Avalonia.Base/Layout/LayoutHelper.cs
+++ b/src/Avalonia.Base/Layout/LayoutHelper.cs
@@ -140,8 +140,7 @@ namespace Avalonia.Layout
///
/// The control.
/// Thrown when control has no root or returned layout scaling is invalid.
- public static double GetLayoutScale(Layoutable control)
- => control.VisualRoot is ILayoutRoot layoutRoot ? layoutRoot.LayoutScaling : 1.0;
+ public static double GetLayoutScale(Layoutable control) => control.GetLayoutRoot()?.LayoutScaling ?? 1.0;
///
/// Rounds a size to integer values for layout purposes, compensating for high DPI screen
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index 411b747107..fa82ff1d31 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -9,6 +9,7 @@ using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Utilities;
+using Avalonia.VisualTree;
#nullable enable
@@ -17,11 +18,10 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- [PrivateApi]
- public class LayoutManager : ILayoutManager, IDisposable
+ internal class LayoutManager : ILayoutManager, IDisposable
{
private const int MaxPasses = 10;
- private readonly Layoutable _owner;
+ private readonly ILayoutRoot _owner;
private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid);
private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid);
private readonly List _toArrangeAfterMeasure = new();
@@ -34,7 +34,7 @@ namespace Avalonia.Layout
public LayoutManager(ILayoutRoot owner)
{
- _owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner));
+ _owner = owner;
_invokeOnRender = ExecuteQueuedLayoutPass;
}
@@ -63,7 +63,7 @@ namespace Avalonia.Layout
#endif
}
- if (control.VisualRoot != _owner)
+ if (control.GetLayoutRoot() != _owner)
{
throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
}
@@ -93,7 +93,7 @@ namespace Avalonia.Layout
#endif
}
- if (control.VisualRoot != _owner)
+ if (control.GetLayoutRoot() != _owner)
{
throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
}
@@ -188,9 +188,12 @@ namespace Avalonia.Layout
try
{
+ if (_owner?.RootVisual == null)
+ return;
+ var root = _owner.RootVisual;
_running = true;
- Measure(_owner);
- Arrange(_owner);
+ Measure(root);
+ Arrange(root);
}
finally
{
@@ -300,7 +303,7 @@ namespace Avalonia.Layout
// control to be removed.
if (!control.IsMeasureValid)
{
- if (control is ILayoutRoot root)
+ if (control.GetLayoutRoot()?.RootVisual == control)
{
control.Measure(Size.Infinity);
}
@@ -329,9 +332,7 @@ namespace Avalonia.Layout
if (!control.IsArrangeValid)
{
- if (control is IEmbeddedLayoutRoot embeddedRoot)
- control.Arrange(new Rect(embeddedRoot.AllocatedSize));
- else if (control is ILayoutRoot root)
+ if (control.GetLayoutRoot()?.RootVisual == control)
control.Arrange(new Rect(control.DesiredSize));
else if (control.PreviousArrange != null)
{
diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 9fa0f7689f..fedea332b6 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -168,7 +168,7 @@ namespace Avalonia.Layout
{
add
{
- if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
+ if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{
r.LayoutManager.RegisterEffectiveViewportListener(this);
}
@@ -180,7 +180,7 @@ namespace Avalonia.Layout
{
_effectiveViewportChanged -= value;
- if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
+ if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r)
{
r.LayoutManager.UnregisterEffectiveViewportListener(this);
}
@@ -194,7 +194,7 @@ namespace Avalonia.Layout
{
add
{
- if (_layoutUpdated is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
+ if (_layoutUpdated is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
}
@@ -206,7 +206,7 @@ namespace Avalonia.Layout
{
_layoutUpdated -= value;
- if (_layoutUpdated is null && VisualRoot is ILayoutRoot r)
+ if (_layoutUpdated is null && this.GetLayoutRoot() is {} r)
{
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
}
@@ -220,7 +220,8 @@ namespace Avalonia.Layout
/// You should not usually need to call this method explictly, the layout manager will
/// schedule layout passes itself.
///
- public void UpdateLayout() => (this.GetVisualRoot() as ILayoutRoot)?.LayoutManager?.ExecuteLayoutPass();
+
+ public void UpdateLayout() => this.GetLayoutManager()?.ExecuteLayoutPass();
///
/// Gets or sets the width of the element.
@@ -448,7 +449,7 @@ namespace Avalonia.Layout
if (IsAttachedToVisualTree)
{
- (VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this);
+ this.GetLayoutManager()?.InvalidateMeasure(this);
InvalidateVisual();
}
OnMeasureInvalidated();
@@ -465,7 +466,7 @@ namespace Avalonia.Layout
Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Invalidated arrange");
IsArrangeValid = false;
- (VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this);
+ this.GetLayoutManager()?.InvalidateArrange(this);
InvalidateVisual();
}
}
@@ -793,7 +794,7 @@ namespace Avalonia.Layout
_isAttachingToVisualTree = false;
}
- if (e.Root is ILayoutRoot r)
+ if (this.GetLayoutRoot() is {} r)
{
if (_layoutUpdated is object)
{
@@ -809,7 +810,7 @@ namespace Avalonia.Layout
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
- if (e.Root is ILayoutRoot r)
+ if (this.GetLayoutRoot() is {} r)
{
if (_layoutUpdated is object)
{
@@ -852,7 +853,7 @@ namespace Avalonia.Layout
// they will need to be registered with the layout manager now that they
// are again effectively visible. If IsEffectivelyVisible becomes an observable
// property then we can piggy-pack on that; for the moment we do this manually.
- if (VisualRoot is ILayoutRoot layoutRoot)
+ if (this.GetLayoutRoot() is {} layoutRoot)
{
var count = VisualChildren.Count;
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index dd81219168..66ee5579c8 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition;
///
internal class CompositingRenderer : IRendererWithCompositor, IHitTester
{
- private readonly IRenderRoot _root;
+ private readonly IPresentationSource _root;
private readonly Compositor _compositor;
private readonly RenderDataDrawingContext _recorder;
private readonly HashSet _dirty = new();
@@ -48,13 +48,12 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
///
/// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
///
- public CompositingRenderer(IRenderRoot root, Compositor compositor, Func> surfaces)
+ public CompositingRenderer(IPresentationSource root, Compositor compositor, Func> surfaces)
{
_root = root;
_compositor = compositor;
_recorder = new(compositor);
CompositionTarget = compositor.CreateCompositionTarget(surfaces);
- CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
_update = Update;
Diagnostics = new RendererDiagnostics();
Diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
@@ -188,13 +187,13 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
commit.Rendered.ContinueWith(_ => Dispatcher.UIThread.Post(() =>
{
_queuedSceneInvalidation = false;
- SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
+ SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(new Rect(_root.ClientSize)));
}, DispatcherPriority.Input), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
}
public void TriggerSceneInvalidatedForUnitTests(Rect rect) =>
- SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, rect));
+ SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(rect));
private void Update()
{
diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
index 6d9b9e23a8..7de58a6dba 100644
--- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
+++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
@@ -25,7 +25,7 @@ public static class ElementComposition
throw new InvalidOperationException("Composition visuals belong to different compositor instances");
visual.ChildCompositionVisual = compositionVisual;
- visual.GetVisualRoot()?.Renderer.RecalculateChildren(visual);
+ visual.GetPresentationSource()?.Renderer.RecalculateChildren(visual);
}
///
diff --git a/src/Avalonia.Base/Rendering/IPresentationSource.cs b/src/Avalonia.Base/Rendering/IPresentationSource.cs
new file mode 100644
index 0000000000..bf5beebe49
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/IPresentationSource.cs
@@ -0,0 +1,46 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Layout;
+using Avalonia.Metadata;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering;
+
+// This interface serves two purposes:
+// 1) User-facing API (public members)
+// 2) A way to provide PresentationSource APIs to Avalonia.Base from Avalonia.Controls
+// without cyclic references (internal members)
+///
+/// Represents the host of the visual tree. On desktop platforms this is typically backed by a native window.
+///
+[NotClientImplementable]
+public interface IPresentationSource
+{
+ ///
+ /// The current root of the visual tree
+ ///
+ public Visual? RootVisual { get; }
+
+ ///
+ /// The scaling factor to use in rendering.
+ ///
+ public double RenderScaling { get; }
+
+ internal IPlatformSettings? PlatformSettings { get; }
+
+ internal IRenderer Renderer { get; }
+
+ internal IHitTester HitTester { get; }
+
+ internal IInputRoot InputRoot { get; }
+
+ internal ILayoutRoot LayoutRoot { get; }
+
+ ///
+ /// Gets the client size of the window.
+ ///
+ internal Size ClientSize { get; }
+
+ internal PixelPoint PointToScreen(Point point);
+ internal Point PointToClient(PixelPoint point);
+}
diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs
deleted file mode 100644
index 820840afbc..0000000000
--- a/src/Avalonia.Base/Rendering/IRenderRoot.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Avalonia.Metadata;
-
-namespace Avalonia.Rendering
-{
- ///
- /// Represents the root of a renderable tree.
- ///
- [NotClientImplementable]
- public interface IRenderRoot
- {
- ///
- /// Gets the client size of the window.
- ///
- Size ClientSize { get; }
-
- ///
- /// Gets the renderer for the window.
- ///
- public IRenderer Renderer { get; }
-
- public IHitTester HitTester { get; }
-
- ///
- /// The scaling factor to use in rendering.
- ///
- double RenderScaling { get; }
-
- ///
- /// Converts a point from screen to client coordinates.
- ///
- /// The point in screen device coordinates.
- /// The point in client coordinates.
- Point PointToClient(PixelPoint point);
-
- ///
- /// Converts a point from client to screen coordinates.
- ///
- /// The point in client coordinates.
- /// The point in screen device coordinates.
- PixelPoint PointToScreen(Point point);
- }
-}
diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs
index 0ca4459c97..56007c7e8c 100644
--- a/src/Avalonia.Base/Rendering/IRenderer.cs
+++ b/src/Avalonia.Base/Rendering/IRenderer.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Rendering
/// Defines the interface for a renderer.
///
[PrivateApi]
- public interface IRenderer : IDisposable
+ internal interface IRenderer : IDisposable
{
///
/// Gets a value indicating whether the renderer should draw specific diagnostics.
@@ -75,7 +75,7 @@ namespace Avalonia.Rendering
}
[PrivateApi]
- public interface IHitTester
+ internal interface IHitTester
{
///
/// Hit tests a location to find the visuals at the specified point.
diff --git a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
index 552ecb14ff..86fbae8158 100644
--- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
+++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
@@ -12,13 +12,9 @@ namespace Avalonia.Rendering
///
/// Initializes a new instance of the class.
///
- /// The render root that has been updated.
/// The updated area.
- public SceneInvalidatedEventArgs(
- IRenderRoot root,
- Rect dirtyRect)
+ public SceneInvalidatedEventArgs(Rect dirtyRect)
{
- RenderRoot = root;
DirtyRect = dirtyRect;
}
@@ -27,9 +23,5 @@ namespace Avalonia.Rendering
///
public Rect DirtyRect { get; }
- ///
- /// Gets the render root that has been invalidated.
- ///
- public IRenderRoot RenderRoot { get; }
}
}
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 843914dc1d..95d55754d3 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -6,10 +6,12 @@ using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Diagnostics;
+using Avalonia.Input;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
+using Avalonia.Platform;
using Avalonia.Reactive;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
@@ -124,7 +126,7 @@ namespace Avalonia
(s, h) => s.Invalidated -= h);
private Rect _bounds;
- private IRenderRoot? _visualRoot;
+ internal IPresentationSource? PresentationSource { get; private set; }
private Visual? _visualParent;
private bool _hasMirrorTransform;
private TargetWeakEventSubscriber? _affectsRenderWeakSubscriber;
@@ -154,8 +156,6 @@ namespace Avalonia
///
public Visual()
{
- _visualRoot = this as IRenderRoot;
-
// Disable transitions until we're added to the visual tree.
DisableTransitions();
@@ -339,7 +339,9 @@ namespace Avalonia
///
/// Gets the root of the visual tree, if the control is attached to a visual tree.
///
- protected internal IRenderRoot? VisualRoot => _visualRoot;
+ protected internal Visual? VisualRoot => PresentationSource?.RootVisual;
+
+ internal IInputRoot? GetInputRoot() => PresentationSource?.InputRoot;
internal RenderOptions RenderOptions
{
@@ -366,7 +368,7 @@ namespace Avalonia
///
/// Gets a value indicating whether this control is attached to a visual root.
///
- internal bool IsAttachedToVisualTree => VisualRoot != null;
+ internal bool IsAttachedToVisualTree => this.PresentationSource != null;
///
/// Gets the control's parent visual.
@@ -409,7 +411,7 @@ namespace Avalonia
///
public void InvalidateVisual()
{
- VisualRoot?.Renderer.AddDirty(this);
+ PresentationSource?.Renderer.AddDirty(this);
}
///
@@ -514,7 +516,7 @@ namespace Avalonia
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
- VisualRoot?.Renderer.RecalculateChildren(this);
+ PresentationSource?.Renderer.RecalculateChildren(this);
}
///
@@ -526,12 +528,8 @@ namespace Avalonia
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Attached to visual tree");
- _visualRoot = e.Root;
+ PresentationSource = e.PresentationSource;
RootedVisualChildrenCount++;
- if (_visualParent is null)
- {
- throw new InvalidOperationException("Visual was attached to the root without being added to the visual parent first.");
- }
if (RenderTransform is IMutableTransform mutableTransform)
{
@@ -539,27 +537,30 @@ namespace Avalonia
}
EnableTransitions();
- if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer)
+ if (PresentationSource.Renderer is IRendererWithCompositor compositingRenderer)
{
AttachToCompositor(compositingRenderer.Compositor);
}
InvalidateMirrorTransform();
- UpdateIsEffectivelyVisible(_visualParent.IsEffectivelyVisible);
+ UpdateIsEffectivelyVisible(_visualParent?.IsEffectivelyVisible ?? true);
OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual();
- _visualRoot.Renderer.RecalculateChildren(_visualParent);
+ if (_visualParent != null)
+ {
+ PresentationSource.Renderer.RecalculateChildren(_visualParent);
- if (ZIndex != 0)
- _visualParent.HasNonUniformZIndexChildren = true;
+ if (ZIndex != 0)
+ _visualParent.HasNonUniformZIndexChildren = true;
+ }
var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
for (var i = 0; i < visualChildrenCount; i++)
{
- if (visualChildren[i] is { } child && child._visualRoot != e.Root) // child may already have been attached within an event handler
+ if (visualChildren[i] is { } child && child.PresentationSource != e.PresentationSource) // child may already have been attached within an event handler
{
child.OnAttachedToVisualTreeCore(e);
}
@@ -574,8 +575,7 @@ namespace Avalonia
protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Detached from visual tree");
-
- _visualRoot = this as IRenderRoot;
+
RootedVisualChildrenCount--;
if (RenderTransform is IMutableTransform mutableTransform)
@@ -589,7 +589,9 @@ namespace Avalonia
DetachFromCompositor();
DetachedFromVisualTree?.Invoke(this, e);
- e.Root.Renderer.AddDirty(this);
+ PresentationSource?.Renderer.AddDirty(this);
+
+ PresentationSource = null;
var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
@@ -686,7 +688,7 @@ namespace Avalonia
parentVisual.HasNonUniformZIndexChildren = true;
sender?.InvalidateVisual();
- parent?.VisualRoot?.Renderer.RecalculateChildren(parent);
+ parent?.PresentationSource?.Renderer.RecalculateChildren(parent);
}
///
@@ -714,17 +716,15 @@ namespace Avalonia
var old = _visualParent;
_visualParent = value;
- if (_visualRoot is not null && old is not null)
+ if (PresentationSource is not null && old is not null)
{
- var e = new VisualTreeAttachmentEventArgs(old, _visualRoot);
+ var e = new VisualTreeAttachmentEventArgs(old, PresentationSource);
OnDetachedFromVisualTreeCore(e);
}
- if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true)
+ if (_visualParent?.IsAttachedToVisualTree == true)
{
- var root = this.FindAncestorOfType() ??
- throw new AvaloniaInternalException("Visual is atached to visual tree but root could not be found.");
- var e = new VisualTreeAttachmentEventArgs(_visualParent, root);
+ var e = new VisualTreeAttachmentEventArgs(_visualParent, _visualParent.PresentationSource!);
OnAttachedToVisualTreeCore(e);
}
@@ -810,5 +810,26 @@ namespace Avalonia
HasMirrorTransform = shouldApplyMirrorTransform;
}
+
+ internal void SetPresentationSourceForRootVisual(IPresentationSource? presentationSource)
+ {
+ if(presentationSource == PresentationSource)
+ return;
+
+ if (PresentationSource != null)
+ {
+ if (presentationSource != null)
+ throw new InvalidOperationException(
+ "Visual is already attached to a presentation source. Only one presentation source can be attached to a visual tree.");
+ OnDetachedFromVisualTreeCore(new(null, PresentationSource));
+ }
+
+ PresentationSource = presentationSource;
+ if(PresentationSource != null)
+ {
+ var e = new VisualTreeAttachmentEventArgs(null, PresentationSource);
+ OnAttachedToVisualTreeCore(e);
+ }
+ }
}
}
diff --git a/src/Avalonia.Base/VisualExtensions.cs b/src/Avalonia.Base/VisualExtensions.cs
index e8dc5465d6..3df2eca039 100644
--- a/src/Avalonia.Base/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualExtensions.cs
@@ -16,10 +16,11 @@ namespace Avalonia
/// The point in client coordinates.
public static Point PointToClient(this Visual visual, PixelPoint point)
{
- var root = visual.VisualRoot ??
- throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
- var rootPoint = root.PointToClient(point);
- return ((Visual)root).TranslatePoint(rootPoint, visual)!.Value;
+ var source = visual.PresentationSource;
+ var root = source?.RootVisual ??
+ throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
+ var rootPoint = source.PointToClient(point);
+ return root.TranslatePoint(rootPoint, visual)!.Value;
}
///
@@ -30,10 +31,11 @@ namespace Avalonia
/// The point in screen coordinates.
public static PixelPoint PointToScreen(this Visual visual, Point point)
{
- var root = visual.VisualRoot ??
- throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
- var p = visual.TranslatePoint(point, (Visual)root);
- return root.PointToScreen(p!.Value);
+ var source = visual.PresentationSource;
+ var root = source?.RootVisual ??
+ throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
+ var p = visual.TranslatePoint(point, root);
+ return source.PointToScreen(p!.Value);
}
///
diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
index b202a2e4b7..b97c15c4df 100644
--- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
+using Avalonia.Layout;
+using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Utilities;
@@ -331,9 +333,10 @@ namespace Avalonia.VisualTree
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
- var root = visual.GetVisualRoot();
+ var source = visual.GetPresentationSource();
+ var root = source?.RootVisual;
- if (root is null)
+ if (source is null || root is null)
{
return null;
}
@@ -342,7 +345,7 @@ namespace Avalonia.VisualTree
if (rootPoint.HasValue)
{
- return root.HitTester.HitTestFirst(rootPoint.Value, visual, filter);
+ return source.HitTester.HitTestFirst(rootPoint.Value, visual, filter);
}
return null;
@@ -380,14 +383,14 @@ namespace Avalonia.VisualTree
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
- var root = visual.GetVisualRoot();
+ var source = visual.GetPresentationSource();
- if (root is null)
+ if (source is null)
{
return Array.Empty();
}
- return root.HitTester.HitTest(p, visual, filter);
+ return source.HitTester.HitTest(p, visual, filter);
}
///
@@ -456,19 +459,26 @@ namespace Avalonia.VisualTree
return visual.VisualParent as T;
}
+
+ public static IPresentationSource? GetPresentationSource(this Visual visual) => visual.PresentationSource;
+
+ // TODO: Verify all usages, this is no longer necessary a TopLevel
+ internal static Visual? GetVisualRoot(this Visual visual) => visual.PresentationSource?.RootVisual;
+
+ internal static ILayoutRoot? GetLayoutRoot(this Visual visual) => visual.PresentationSource?.LayoutRoot;
+
///
- /// Gets the root visual for an .
+ /// Gets the layout manager for the visual's presentation source, or null if the visual is not attached to a visual root.
///
- /// The visual.
- ///
- /// The root visual or null if the visual is not rooted.
- ///
- public static IRenderRoot? GetVisualRoot(this Visual visual)
- {
- ThrowHelper.ThrowIfNull(visual, nameof(visual));
+ public static ILayoutManager? GetLayoutManager(this Visual visual) =>
+ visual.PresentationSource?.LayoutRoot.LayoutManager;
- return visual as IRenderRoot ?? visual.VisualRoot;
- }
+ ///
+ /// Attempts to obtain platform settings from the visual's root.
+ /// This will return null if the visual is not attached to a visual root.
+ ///
+ public static IPlatformSettings? GetPlatformSettings(this Visual visual) =>
+ visual.GetPresentationSource()?.PlatformSettings;
///
/// Returns a value indicating whether this control is attached to a visual root.
diff --git a/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs b/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs
index c0e8b1613f..8b644e1b25 100644
--- a/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs
+++ b/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using Avalonia.Rendering;
namespace Avalonia
@@ -12,22 +13,37 @@ namespace Avalonia
///
/// Initializes a new instance of the class.
///
- /// The parent that the visual is being attached to or detached from.
- /// The root visual.
- public VisualTreeAttachmentEventArgs(Visual parent, IRenderRoot root)
+ /// The parent that the visual's tree is being attached to or detached from.
+ /// Presentation source this visual is being attached to.
+ public VisualTreeAttachmentEventArgs(Visual? attachmentPoint, IPresentationSource presentationSource)
{
- Parent = parent ?? throw new ArgumentNullException(nameof(parent));
- Root = root ?? throw new ArgumentNullException(nameof(root));
+ RootVisual = presentationSource.RootVisual ??
+ throw new InvalidOperationException("PresentationSource must have a non-null RootVisual.");
+ AttachmentPoint = attachmentPoint;
+ PresentationSource = presentationSource ?? throw new ArgumentNullException(nameof(presentationSource));
}
///
- /// Gets the parent that the visual is being attached to or detached from.
+ /// Gets the parent that the visual's tree is being attached to or detached from, null means that
+ /// the entire tree is being attached to a PresentationSource
///
- public Visual Parent { get; }
+ public Visual? AttachmentPoint { get; }
+
+ [Obsolete("Use " + nameof(AttachmentPoint))]
+ public Visual? Parent => AttachmentPoint;
///
/// Gets the root of the visual tree that the visual is being attached to or detached from.
///
- public IRenderRoot Root { get; }
+ public IPresentationSource PresentationSource { get; }
+
+ [Obsolete("This was previously always returning TopLevel. This is no longer guaranteed. Use TopLevel.GetTopLevel(this) if you need a TopLevel or args.RootVisual if you are interested in the root of the visual tree.")]
+ public Visual Root => RootVisual;
+
+ ///
+ /// The root visual of the tree this visual is being attached to or detached from.
+ /// This is guaranteed to be non-null and will be the same as .
+ ///
+ public Visual RootVisual { get; set; }
}
}
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 3652acac45..9e2d1725ce 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -208,14 +208,14 @@ namespace Avalonia.Controls
if (IsDefault)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
ListenForDefault(inputElement);
}
}
if (IsCancel)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
ListenForCancel(inputElement);
}
@@ -229,14 +229,14 @@ namespace Avalonia.Controls
if (IsDefault)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
StopListeningForDefault(inputElement);
}
}
if (IsCancel)
{
- if (e.Root is IInputElement inputElement)
+ if (e.RootVisual is IInputElement inputElement)
{
StopListeningForCancel(inputElement);
}
diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs
index 5eb65d3396..430a8d4eaf 100644
--- a/src/Avalonia.Controls/Chrome/TitleBar.cs
+++ b/src/Avalonia.Controls/Chrome/TitleBar.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Controls.Chrome
_captionButtons = e.NameScope.Get("PART_CaptionButtons");
- if (VisualRoot is Window window)
+ if (TopLevel.GetTopLevel(this) is Window window)
{
_captionButtons?.Attach(window);
@@ -67,7 +67,7 @@ namespace Avalonia.Controls.Chrome
{
base.OnAttachedToVisualTree(e);
- if (VisualRoot is Window window)
+ if (TopLevel.GetTopLevel(this) is Window window)
{
_disposables = new CompositeDisposable(6)
{
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 86afcd06b9..6a6b9f6083 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -509,7 +509,7 @@ namespace Avalonia.Controls
if (e.Source == this
&& !e.Handled)
{
- var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration.OpenContextMenu;
+ var keymap = this.GetPlatformSettings()?.HotkeyConfiguration.OpenContextMenu;
if (keymap is null)
{
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index 2f88e662a2..0704d1325d 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -8,6 +8,7 @@ using Avalonia.Layout;
using System;
using System.Collections.Generic;
using System.Globalization;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -406,7 +407,7 @@ namespace Avalonia.Controls
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
- (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+ this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
index ca5c43463d..95d9e82782 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
@@ -7,6 +7,7 @@ using System;
using System.Globalization;
using Avalonia.Controls.Utils;
using Avalonia.Automation.Peers;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -380,7 +381,7 @@ namespace Avalonia.Controls
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
- (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+ this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
index 675b22edc7..ead981cdb6 100644
--- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
+++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
@@ -16,8 +16,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner();
- public static readonly StyledProperty MaterialProperty =
- AvaloniaProperty.Register(nameof(Material));
+ public static readonly StyledProperty MaterialProperty =
+ AvaloniaProperty.Register(nameof(Material));
private IDisposable? _subscription;
private IDisposable? _materialSubscription;
@@ -39,7 +39,7 @@ namespace Avalonia.Controls
set => SetValue(CornerRadiusProperty, value);
}
- public ExperimentalAcrylicMaterial Material
+ public ExperimentalAcrylicMaterial? Material
{
get => GetValue(MaterialProperty);
set => SetValue(MaterialProperty, value);
@@ -49,20 +49,29 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
- var tl = (TopLevel)e.Root;
-
- _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
- .Subscribe(x =>
- {
- if (tl.PlatformImpl is null)
- return;
- if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None)
- Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
- else if (x == WindowTransparencyLevel.Blur)
- Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
- else if (x == WindowTransparencyLevel.AcrylicBlur)
- Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
- });
+ var tl = TopLevel.GetTopLevel(this);
+ if (tl != null)
+ {
+
+ _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
+ .Subscribe(x =>
+ {
+ if (tl.PlatformImpl is null || Material is null)
+ return;
+ if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None)
+ Material.PlatformTransparencyCompensationLevel =
+ tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
+ else if (x == WindowTransparencyLevel.Blur)
+ Material.PlatformTransparencyCompensationLevel =
+ tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
+ else if (x == WindowTransparencyLevel.AcrylicBlur)
+ Material.PlatformTransparencyCompensationLevel =
+ tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
+ });
+ }
+ else if (Material != null)
+ Material.PlatformTransparencyCompensationLevel = 1;
+
UpdateMaterialSubscription();
}
@@ -86,7 +95,9 @@ namespace Avalonia.Controls
if (visual is CompositionExperimentalAcrylicVisual v)
{
v.CornerRadius = CornerRadius;
- v.Material = (ImmutableExperimentalAcrylicMaterial)Material.ToImmutable();
+ v.Material = (Material?.ToImmutable()) is ImmutableExperimentalAcrylicMaterial material
+ ? material
+ : default(ImmutableExperimentalAcrylicMaterial);
}
}
diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
index b3ff18320a..dd0b5810bf 100644
--- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
@@ -344,7 +344,7 @@ namespace Avalonia.Controls.Primitives
return;
}
- if (Popup?.Host is PopupRoot && pArgs.Root is Visual eventRoot)
+ if (Popup?.Host is PopupRoot && pArgs.Root.RootElement is {} eventRoot)
{
// As long as the pointer stays within the enlargedPopupRect
// the flyout stays open. If it leaves, close it
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index df70cdf6d0..d181328134 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -2117,7 +2117,7 @@ namespace Avalonia.Controls
{
// DpiScale dpiScale = GetDpi();
// double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY;
- var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
+ var dpi = this.GetLayoutRoot()?.LayoutScaling ?? 1.0;
double[] roundingErrors = RoundingErrors;
double roundedTakenSize = 0;
diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs
index ba857687f6..df502207c6 100644
--- a/src/Avalonia.Controls/GridSplitter.cs
+++ b/src/Avalonia.Controls/GridSplitter.cs
@@ -13,6 +13,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -226,7 +227,7 @@ namespace Avalonia.Controls
ResizeDirection = resizeDirection,
SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection),
- Scaling = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1,
+ Scaling = this.GetLayoutRoot()?.LayoutScaling ?? 1,
};
// Store the rows and columns to resize on drag events.
diff --git a/src/Avalonia.Controls/IMenu.cs b/src/Avalonia.Controls/IMenu.cs
index b3ec77b108..426955b682 100644
--- a/src/Avalonia.Controls/IMenu.cs
+++ b/src/Avalonia.Controls/IMenu.cs
@@ -23,6 +23,6 @@ namespace Avalonia.Controls
///
/// Gets the root of the visual tree, if the control is attached to a visual tree.
///
- IRenderRoot? VisualRoot { get; }
+ TopLevel? TopLevel { get; }
}
}
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 9ef4b25c53..3ef75f4990 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -128,7 +128,7 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e)
{
- var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration;
+ var hotkeys = this.GetPlatformSettings()?.HotkeyConfiguration;
var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
if (!ctrl &&
diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs
index e134c5db19..08855d253c 100644
--- a/src/Avalonia.Controls/MaskedTextBox.cs
+++ b/src/Avalonia.Controls/MaskedTextBox.cs
@@ -6,6 +6,7 @@ using System.Linq;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -203,7 +204,7 @@ namespace Avalonia.Controls
return;
}
- var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
+ var keymap = this.GetPlatformSettings()?.HotkeyConfiguration;
bool Match(List gestures) => gestures.Any(g => g.Matches(e));
diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs
index 7ee2b27a52..c1c869e41a 100644
--- a/src/Avalonia.Controls/Menu.cs
+++ b/src/Avalonia.Controls/Menu.cs
@@ -88,7 +88,7 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
- var inputRoot = e.Root as TopLevel;
+ var inputRoot = TopLevel.GetTopLevel(this);
if (inputRoot?.AccessKeyHandler != null)
{
diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs
index 2c0792c028..bde5bb17de 100644
--- a/src/Avalonia.Controls/MenuBase.cs
+++ b/src/Avalonia.Controls/MenuBase.cs
@@ -75,7 +75,7 @@ namespace Avalonia.Controls
///
IMenuInteractionHandler IMenu.InteractionHandler => InteractionHandler;
- IRenderRoot? IMenu.VisualRoot => VisualRoot;
+ TopLevel? IMenu.TopLevel => TopLevel.GetTopLevel(this);
///
IMenuItem? IMenuElement.SelectedItem
diff --git a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
index 5571773a9d..1be05fbe36 100644
--- a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
+++ b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Controls
///
internal class MenuItemAccessKeyHandler : AccessKeyHandler
{
- protected override void OnSetOwner(IInputRoot owner)
+ protected override void OnSetOwner(InputElement owner)
{
owner.AddHandler(InputElement.TextInputEvent, OnTextInput);
}
diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs
index a6ad90dfcb..e3aaa22b5c 100644
--- a/src/Avalonia.Controls/NativeControlHost.cs
+++ b/src/Avalonia.Controls/NativeControlHost.cs
@@ -5,6 +5,7 @@ using Avalonia.Automation.Peers;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
+using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.VisualTree;
@@ -12,7 +13,7 @@ namespace Avalonia.Controls
{
public class NativeControlHost : Control
{
- private TopLevel? _currentRoot;
+ private PresentationSource? _currentRoot;
private INativeControlHostImpl? _currentHost;
private INativeControlHostControlTopLevelAttachment? _attachment;
private IPlatformHandle? _nativeControlHandle;
@@ -43,7 +44,7 @@ namespace Avalonia.Controls
///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
- _currentRoot = e.Root as TopLevel;
+ _currentRoot = (PresentationSource)e.PresentationSource;
var visual = (Visual)this;
while (visual != null)
{
@@ -147,7 +148,7 @@ namespace Avalonia.Controls
var bounds = Bounds;
// Native window is not rendered by Avalonia
- var transformToVisual = this.TransformToVisual(_currentRoot);
+ var transformToVisual = _currentRoot.RootVisual != null ? this.TransformToVisual(_currentRoot.RootVisual) : null;
if (transformToVisual == null)
return null;
var position = new Rect(default, bounds.Size).TransformToAABB(transformToVisual.Value).Position;
diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs
index 2f271ef34c..022511bafa 100644
--- a/src/Avalonia.Controls/NativeMenuBar.cs
+++ b/src/Avalonia.Controls/NativeMenuBar.cs
@@ -34,7 +34,7 @@ namespace Avalonia.Controls
?? this.FindDescendantOfType()
?? throw new InvalidOperationException("NativeMenuBar requires a MenuBase#PART_NativeMenuPresenter template part.");
- if (VisualRoot is TopLevel topLevel)
+ if (TopLevel.GetTopLevel(this) is {} topLevel)
{
SubscribeToToplevel(topLevel, _menu);
}
@@ -47,7 +47,7 @@ namespace Avalonia.Controls
if (_menu is null)
return;
- if (e.Root is TopLevel topLevel)
+ if (TopLevel.GetTopLevel(this) is {} topLevel)
{
SubscribeToToplevel(topLevel, _menu);
}
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index 8b699e2df7..f8d8c43d3c 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Controls.Platform
{
private readonly bool _isContextMenu;
private IDisposable? _inputManagerSubscription;
- private IRenderRoot? _root;
+ private TopLevel? _root;
private RadioButtonGroupManager? _groupManager;
public DefaultMenuInteractionHandler(bool isContextMenu)
@@ -300,7 +300,7 @@ namespace Avalonia.Controls.Platform
Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
- _root = Menu.VisualRoot;
+ _root = Menu.TopLevel;
if (_root is not null)
{
diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
index 615decc753..1a7b719499 100644
--- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs
+++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Platform
private DragDropEffects _allowedEffects;
private IDataTransfer? _draggedData;
- private TopLevel? _lastRoot;
+ private PresentationSource? _lastSource;
private Point _lastPosition;
private StandardCursorType? _lastCursorType;
private RawInputModifiers? _initialInputModifiers;
@@ -40,7 +40,7 @@ namespace Avalonia.Platform
if (_draggedData == null)
{
_draggedData = dataTransfer;
- _lastRoot = null;
+ _lastSource = null;
_lastPosition = default;
_allowedEffects = allowedEffects;
@@ -75,11 +75,12 @@ namespace Avalonia.Platform
_lastPosition = pt;
RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData!, _allowedEffects, modifiers);
- var tl = (root as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault();
- tl?.PlatformImpl?.Input?.Invoke(rawEvent);
+ var source = root.RootElement.PresentationSource as PresentationSource;
+
+ source?.PlatformImpl?.Input?.Invoke(rawEvent);
var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
- UpdateCursor(tl, effect);
+ UpdateCursor(source, effect);
return effect;
}
@@ -105,12 +106,12 @@ namespace Avalonia.Platform
return StandardCursorType.No;
}
- private void UpdateCursor(TopLevel? root, DragDropEffects effect)
+ private void UpdateCursor(PresentationSource? root, DragDropEffects effect)
{
- if (_lastRoot != root)
+ if (_lastSource != root)
{
- _lastRoot?.SetCursorOverride(null);
- _lastRoot = root;
+ _lastSource?.SetCursorOverride(null);
+ _lastSource = root;
_lastCursorType = null;
}
@@ -127,8 +128,8 @@ namespace Avalonia.Platform
private void CancelDragging()
{
- if (_lastRoot != null)
- RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, RawInputModifiers.None);
+ if (_lastSource != null)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, _lastPosition, RawInputModifiers.None);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
}
@@ -137,16 +138,16 @@ namespace Avalonia.Platform
{
if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape)
{
- if (_lastRoot != null)
- RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers);
+ if (_lastSource != null)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, _lastPosition, e.Modifiers);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
e.Handled = true;
}
else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
{
- if (_lastRoot != null)
- RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers);
+ if (_lastSource != null)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastSource, _lastPosition, e.Modifiers);
}
}
@@ -195,10 +196,10 @@ namespace Avalonia.Platform
return;
}
- if (e.Root != _lastRoot)
+ if (e.Root != _lastSource)
{
- if (_lastRoot is Visual lr && e.Root is Visual r)
- RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, lr.PointToClient(r.PointToScreen(e.Position)), e.InputModifiers);
+ if (_lastSource?.RootElement is Visual lr && e.Root.RootElement is Visual r)
+ RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, lr.PointToClient(r.PointToScreen(e.Position)), e.InputModifiers);
RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers);
}
else
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.Cursor.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.Cursor.cs
new file mode 100644
index 0000000000..6acc904626
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.Cursor.cs
@@ -0,0 +1,46 @@
+using Avalonia.Input;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource
+{
+ private Cursor? _cursor;
+ private Cursor? _cursorOverride;
+
+ private void UpdateCursor() => PlatformImpl?.SetCursor(_cursorOverride?.PlatformImpl ?? _cursor?.PlatformImpl);
+
+ private void SetCursor(Cursor? cursor)
+ {
+ _cursor = cursor;
+ UpdateCursor();
+ }
+
+ ///
+ /// This should only be used by InProcessDragSource
+ ///
+ internal void SetCursorOverride(Cursor? cursor)
+ {
+ _cursorOverride = cursor;
+ UpdateCursor();
+ }
+
+ IInputElement? IInputRoot.PointerOverElement
+ {
+ get => field;
+ set
+ {
+ if (field is AvaloniaObject old)
+ old.PropertyChanged -= PointerOverElement_PropertyChanged;
+ field = value;
+ if (field is AvaloniaObject @new)
+ @new.PropertyChanged += PointerOverElement_PropertyChanged;
+ SetCursor(value?.Cursor);
+ }
+ }
+
+ private void PointerOverElement_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == InputElement.CursorProperty)
+ SetCursor((sender as IInputElement)?.Cursor);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.Input.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.Input.cs
new file mode 100644
index 0000000000..f153fe628b
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.Input.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Threading;
+using Avalonia.Diagnostics;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource
+{
+ public IInputRoot InputRoot => this;
+
+ ///
+ /// Handles input from .
+ ///
+ private void HandleInputCore(object? state)
+ {
+ using var _ = Diagnostic.BeginLayoutInputPass();
+
+ var e = (RawInputEventArgs)state!;
+ if (e is RawPointerEventArgs pointerArgs)
+ {
+ var hitTestElement = RootElement.InputHitTest(pointerArgs.Position, enabledElementsOnly: false);
+
+ pointerArgs.InputHitTestResult = (hitTestElement, FirstEnabledAncestor(hitTestElement));
+ }
+
+ _inputManager?.ProcessInput(e);
+ }
+
+ private SendOrPostCallback _handleInputCore;
+
+ private void HandleInput(RawInputEventArgs e)
+ {
+ if (PlatformImpl != null)
+ {
+ Dispatcher.UIThread.Send(_handleInputCore, e);
+ }
+ else
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
+ this,
+ "PlatformImpl is null, couldn't handle input.");
+ }
+ }
+
+
+ private static IInputElement? FirstEnabledAncestor(IInputElement? hitTestElement)
+ {
+ var candidate = hitTestElement;
+ while (candidate?.IsEffectivelyEnabled == false)
+ {
+ candidate = (candidate as Visual)?.VisualParent as IInputElement;
+ }
+ return candidate;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs
new file mode 100644
index 0000000000..156c76cc6f
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs
@@ -0,0 +1,69 @@
+using System;
+using System.ComponentModel;
+using Avalonia.Layout;
+using Avalonia.Rendering;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource : ILayoutRoot
+{
+ private LayoutDiagnosticBridge? _layoutDiagnosticBridge;
+ public double LayoutScaling => RenderScaling;
+ public ILayoutManager LayoutManager { get; }
+ ILayoutRoot IPresentationSource.LayoutRoot => this;
+ Layoutable ILayoutRoot.RootVisual => RootVisual;
+
+ private ILayoutManager CreateLayoutManager()
+ {
+ var manager = new LayoutManager(this);
+ _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, manager);
+ _layoutDiagnosticBridge.SetupBridge();
+ return manager;
+ }
+
+
+ ///
+ /// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes.
+ ///
+ private sealed class LayoutDiagnosticBridge : IDisposable
+ {
+ private readonly RendererDiagnostics _diagnostics;
+ private readonly LayoutManager _layoutManager;
+ private bool _isHandling;
+
+ public LayoutDiagnosticBridge(RendererDiagnostics diagnostics, LayoutManager layoutManager)
+ {
+ _diagnostics = diagnostics;
+ _layoutManager = layoutManager;
+
+ diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
+ }
+
+ public void SetupBridge()
+ {
+ var needsHandling = (_diagnostics.DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0;
+ if (needsHandling != _isHandling)
+ {
+ _isHandling = needsHandling;
+ _layoutManager.LayoutPassTimed = needsHandling
+ ? timing => _diagnostics.LastLayoutPassTiming = timing
+ : null;
+ }
+ }
+
+ private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(RendererDiagnostics.DebugOverlays))
+ {
+ SetupBridge();
+ }
+ }
+
+ public void Dispose()
+ {
+ _diagnostics.PropertyChanged -= OnDiagnosticsPropertyChanged;
+ _layoutManager.LayoutPassTimed = null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs
new file mode 100644
index 0000000000..d017cb3f5c
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs
@@ -0,0 +1,29 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource
+{
+ private readonly Func _clientSizeProvider;
+ public CompositingRenderer Renderer { get; }
+ IRenderer IPresentationSource.Renderer => Renderer;
+ Visual IPresentationSource.RootVisual => RootVisual;
+ public IHitTester HitTester => HitTesterOverride ?? Renderer;
+ //TODO: Can we PLEASE get rid of this abomination in tests and use actual hit-testing engine instead?
+ public IHitTester? HitTesterOverride { get; set; }
+
+ public double RenderScaling => PlatformImpl?.RenderScaling ?? 1;
+ public Size ClientSize => _clientSizeProvider();
+
+ public void SceneInvalidated(object? sender, SceneInvalidatedEventArgs sceneInvalidatedEventArgs)
+ {
+ _pointerOverPreProcessor?.SceneInvalidated(sceneInvalidatedEventArgs.DirtyRect);
+ }
+
+ public PixelPoint PointToScreen(Point point) => PlatformImpl?.PointToScreen(point) ?? default;
+
+ public Point PointToClient(PixelPoint point) => PlatformImpl?.PointToClient(point) ?? default;
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs
new file mode 100644
index 0000000000..0c400dff81
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs
@@ -0,0 +1,116 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Input.TextInput;
+using Avalonia.Layout;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource : IPresentationSource, IInputRoot, IDisposable
+{
+ public ITopLevelImpl? PlatformImpl { get; private set; }
+ private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
+ private readonly IDisposable? _pointerOverPreProcessorSubscription;
+ private readonly IInputManager? _inputManager;
+
+
+ internal FocusManager FocusManager { get; } = new();
+
+ public PresentationSource(InputElement rootVisual, ITopLevelImpl platformImpl,
+ IAvaloniaDependencyResolver dependencyResolver, Func clientSizeProvider)
+ {
+ _clientSizeProvider = clientSizeProvider;
+
+ PlatformImpl = platformImpl;
+
+
+ _inputManager = TryGetService(dependencyResolver);
+ _handleInputCore = HandleInputCore;
+
+ PlatformImpl.SetInputRoot(this);
+ PlatformImpl.Input = HandleInput;
+
+ _pointerOverPreProcessor = new PointerOverPreProcessor(this);
+ _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
+
+ Renderer = new CompositingRenderer(this, PlatformImpl.Compositor, () => PlatformImpl.Surfaces ?? []);
+ Renderer.SceneInvalidated += SceneInvalidated;
+ LayoutManager = CreateLayoutManager();
+
+ RootVisual = rootVisual;
+ }
+
+ // In WPF it's a Visual and it's nullable. For now we have it as non-nullable InputElement since
+ // there are way too many things to update at once and the current goal is to decouple
+ // "visual tree root" concept from TopLevel
+ public InputElement RootVisual
+ {
+ get => field;
+ set
+ {
+ field?.SetPresentationSourceForRootVisual(null);
+ field = value;
+
+ field?.SetPresentationSourceForRootVisual(this);
+ Renderer.CompositionTarget.Root = field?.CompositionVisual;
+
+ FocusManager.SetContentRoot(value as IInputElement);
+ }
+ }
+
+
+ IFocusManager? IInputRoot.FocusManager => FocusManager;
+
+ IPlatformSettings? IPresentationSource.PlatformSettings => AvaloniaLocator.Current.GetService();
+
+ ITextInputMethodImpl? IInputRoot.InputMethod => PlatformImpl?.TryGetFeature();
+ public InputElement RootElement => RootVisual;
+
+
+ public void Dispose()
+ {
+ _layoutDiagnosticBridge?.Dispose();
+ _layoutDiagnosticBridge = null;
+ LayoutManager.Dispose();
+ Renderer.SceneInvalidated -= SceneInvalidated;
+ // We need to wait for the renderer to complete any in-flight operations
+ Renderer.Dispose();
+
+ PlatformImpl = null;
+ _pointerOverPreProcessor?.OnCompleted();
+ _pointerOverPreProcessorSubscription?.Dispose();
+ if (((IInputRoot)this).PointerOverElement is AvaloniaObject pointerOverElement)
+ pointerOverElement.PropertyChanged -= PointerOverElement_PropertyChanged;
+ }
+
+ ///
+ /// Tries to get a service from an , logging a
+ /// warning if not found.
+ ///
+ /// The service type.
+ /// The resolver.
+ /// The service.
+ private T? TryGetService(IAvaloniaDependencyResolver resolver) where T : class
+ {
+ var result = resolver.GetService();
+
+ if (result == null)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
+ this,
+ "Could not create {Service} : maybe Application.RegisterServices() wasn't called?",
+ typeof(T));
+ }
+
+ return result;
+ }
+
+ // TODO: Make popup positioner to use PresentationSource internally rather than TopLevel
+ public PixelPoint? GetLastPointerPosition(Visual topLevel)
+ {
+ return _pointerOverPreProcessor?.LastPosition;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index 7f59c9e570..da90a2e28a 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the attached property.
///
public static readonly AttachedProperty ShowAccessKeyProperty =
- AvaloniaProperty.RegisterAttached("ShowAccessKey", inherits: true);
+ AccessKeyHandler.ShowAccessKeyProperty.AddOwner();
///
/// The access key handler for the current window.
@@ -92,7 +92,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
- _accessKeys = (e.Root as TopLevel)?.AccessKeyHandler;
+ _accessKeys = TopLevel.GetTopLevel(this)?.AccessKeyHandler;
if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
diff --git a/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs b/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs
index b7a41678db..196e2b98ed 100644
--- a/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs
+++ b/src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs
@@ -1,6 +1,7 @@
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives;
@@ -75,7 +76,7 @@ public static class ItemSelectionEventTriggers
public static bool HasToggleSelectionModifier(Visual selectable, RoutedEventArgs eventArgs) => HasModifiers(eventArgs, Hotkeys(selectable)?.CommandModifiers);
private static PlatformHotkeyConfiguration? Hotkeys(Visual element) =>
- (TopLevel.GetTopLevel(element)?.PlatformSettings ?? Application.Current?.PlatformSettings)?.HotkeyConfiguration;
+ (element.GetPlatformSettings() ?? Application.Current?.PlatformSettings)?.HotkeyConfiguration;
private static bool HasModifiers(RoutedEventArgs eventArgs, KeyModifiers? modifiers) =>
modifiers != null && eventArgs is IKeyModifiersEventArgs { KeyModifiers: { } eventModifiers } && eventModifiers.HasAllFlags(modifiers.Value);
diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
index d52e0e4d98..cbbcdb12d7 100644
--- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics;
using Avalonia.Input;
+using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
@@ -10,7 +11,7 @@ using Avalonia.Platform;
namespace Avalonia.Controls.Primitives
{
- public class OverlayPopupHost : ContentControl, IPopupHost, IManagedPopupPositionerPopup, IInputRoot
+ public class OverlayPopupHost : ContentControl, IPopupHost, IManagedPopupPositionerPopup
{
///
/// Defines the property.
@@ -21,6 +22,7 @@ namespace Avalonia.Controls.Primitives
private readonly OverlayLayer _overlayLayer;
private readonly ManagedPopupPositioner _positioner;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
+ internal IKeyboardNavigationHandler Tests_KeyboardNavigationHandler => _keyboardNavigationHandler!;
private Point _lastRequestedPosition;
private PopupPositionRequest? _popupPositionRequest;
private Size _popupSize;
@@ -59,39 +61,7 @@ namespace Avalonia.Controls.Primitives
get => false;
set { /* Not currently supported in overlay popups */ }
}
-
- private IInputRoot? InputRoot
- => TopLevel.GetTopLevel(this);
-
- IKeyboardNavigationHandler? IInputRoot.KeyboardNavigationHandler
- => _keyboardNavigationHandler;
-
- IFocusManager? IInputRoot.FocusManager
- => InputRoot?.FocusManager;
-
- IPlatformSettings? IInputRoot.PlatformSettings
- => InputRoot?.PlatformSettings;
-
- IInputElement? IInputRoot.PointerOverElement
- {
- get => InputRoot?.PointerOverElement;
- set
- {
- if (InputRoot is { } inputRoot)
- inputRoot.PointerOverElement = value;
- }
- }
-
- bool IInputRoot.ShowAccessKeys
- {
- get => InputRoot?.ShowAccessKeys ?? false;
- set
- {
- if (InputRoot is { } inputRoot)
- inputRoot.ShowAccessKeys = value;
- }
- }
-
+
///
internal override Interactive? InteractiveParent => Parent as Interactive;
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 5294df72d5..28b3f03982 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -676,7 +676,7 @@ namespace Avalonia.Controls.Primitives
{
var newTarget = change.GetNewValue() ?? this.FindLogicalAncestorOfType();
- if (newTarget is null || newTarget.GetVisualRoot() != _openState.TopLevel)
+ if (newTarget is null || TopLevel.GetTopLevel(newTarget) != _openState.TopLevel)
{
Close();
return;
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
index 2f735ef691..ea38f7c7e3 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
@@ -574,7 +574,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
if (matrix == null)
{
- if (target.GetVisualRoot() == null)
+ if (!target.IsAttachedToVisualTree)
throw new InvalidOperationException("Target control is not attached to the visual tree");
throw new InvalidOperationException("Target control is not in the same tree as the popup parent");
}
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 6dc4627f0c..13b7d3a839 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -2,6 +2,7 @@ using System;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics;
+using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs
index 1ce36f14b5..8995f4762a 100644
--- a/src/Avalonia.Controls/RadioButton.cs
+++ b/src/Avalonia.Controls/RadioButton.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_groupManager?.Remove(this, GroupName);
- EnsureRadioGroupManager(e.Root);
+ EnsureRadioGroupManager(e.PresentationSource);
base.OnAttachedToVisualTree(e);
}
@@ -106,9 +106,9 @@ namespace Avalonia.Controls
}
[MemberNotNull(nameof(_groupManager))]
- private void EnsureRadioGroupManager(IRenderRoot? root = null)
+ private void EnsureRadioGroupManager(IPresentationSource? source = null)
{
- _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(root ?? this.GetVisualRoot());
+ _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(source ?? this.GetPresentationSource());
_groupManager.Add(this);
}
}
diff --git a/src/Avalonia.Controls/RadioButtonGroupManager.cs b/src/Avalonia.Controls/RadioButtonGroupManager.cs
index 80a942a2f0..c3d744c7c8 100644
--- a/src/Avalonia.Controls/RadioButtonGroupManager.cs
+++ b/src/Avalonia.Controls/RadioButtonGroupManager.cs
@@ -18,12 +18,12 @@ internal interface IRadioButton : ILogical
internal class RadioButtonGroupManager
{
private static readonly RadioButtonGroupManager s_default = new();
- private static readonly ConditionalWeakTable s_registeredVisualRoots = new();
+ private static readonly ConditionalWeakTable