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 s_registeredVisualRoots = new(); private readonly Dictionary>> _registeredGroups = new(); private bool _ignoreCheckedChanges; - public static RadioButtonGroupManager GetOrCreateForRoot(IRenderRoot? root) + public static RadioButtonGroupManager GetOrCreateForRoot(object? root) { if (root == null) return s_default; diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index f2fef55d28..5712ad5564 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -265,7 +265,7 @@ namespace Avalonia.Controls.Remote.Server protected virtual Size Measure(Size constraint) { - var l = (Layoutable) InputRoot!; + var l = InputRoot!.RootElement; l.Measure(constraint); return l.DesiredSize; } diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index d99d185bed..1f09e1777a 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -38,12 +38,12 @@ namespace Avalonia.Controls if (e is RawPointerEventArgs pointerEvent) { bool isTooltipEvent = false; - if (_tipControl?.GetValue(ToolTip.ToolTipProperty) is { } currentTip && e.Root == currentTip.PopupHost) + if (_tipControl?.GetValue(ToolTip.ToolTipProperty) is { } currentTip && e.Root.RootElement == currentTip.PopupHost) { isTooltipEvent = true; _lastTipEventTime = pointerEvent.Timestamp; } - else if (e.Root == _tipControl?.VisualRoot) + else if (e.Root.RootElement == _tipControl?.VisualRoot) { _lastWindowEventTime = pointerEvent.Timestamp; } @@ -53,7 +53,7 @@ namespace Avalonia.Controls case RawPointerEventType.Move: Update(pointerEvent.Root, pointerEvent.InputHitTestResult.element as Visual); break; - case RawPointerEventType.LeaveWindow when (e.Root == _tipControl?.VisualRoot && _lastTipEventTime != e.Timestamp) || (isTooltipEvent && _lastWindowEventTime != e.Timestamp): + case RawPointerEventType.LeaveWindow when (e.Root.RootElement == _tipControl?.VisualRoot && _lastTipEventTime != e.Timestamp) || (isTooltipEvent && _lastWindowEventTime != e.Timestamp): ClearTip(); _tipControl = null; break; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 313f9969d6..2304ccb63c 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -39,13 +39,9 @@ namespace Avalonia.Controls /// [TemplatePart("PART_TransparencyFallback", typeof(Border))] public abstract class TopLevel : ContentControl, - IInputRoot, - ILayoutRoot, - IRenderRoot, ICloseable, IStyleHost, - ILogicalRoot, - ITextInputMethodRoot + ILogicalRoot { /// /// Defines the property. @@ -59,12 +55,6 @@ namespace Avalonia.Controls public static readonly DirectProperty FrameSizeProperty = AvaloniaProperty.RegisterDirect(nameof(FrameSize), o => o.FrameSize); - /// - /// Defines the property. - /// - public static readonly StyledProperty PointerOverElementProperty = - AvaloniaProperty.Register(nameof(IInputRoot.PointerOverElement)); - /// /// Defines the property. /// @@ -125,24 +115,22 @@ namespace Avalonia.Controls private readonly IToolTipService? _tooltipService; private readonly IAccessKeyHandler? _accessKeyHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; + internal IKeyboardNavigationHandler Tests_KeyboardNavigationHandler => _keyboardNavigationHandler!; private readonly IGlobalStyles? _globalStyles; private readonly IThemeVariantHost? _applicationThemeHost; - private readonly PointerOverPreProcessor? _pointerOverPreProcessor; - private readonly IDisposable? _pointerOverPreProcessorSubscription; private readonly IDisposable? _backGestureSubscription; private readonly Dictionary _platformImplBindings = new(); private double _scaling; private Size _clientSize; private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; - private ILayoutManager? _layoutManager; private Border? _transparencyFallbackBorder; private TargetWeakEventSubscriber? _resourcesChangesSubscriber; private IStorageProvider? _storageProvider; private Screens? _screens; - private LayoutDiagnosticBridge? _layoutDiagnosticBridge; - private Cursor? _cursor; - private Cursor? _cursorOverride; + private readonly PresentationSource _source; + internal new PresentationSource PresentationSource => _source; + internal IInputRoot InputRoot => _source; /// /// Initializes static members of the class. @@ -176,19 +164,6 @@ namespace Avalonia.Controls topLevel?.InvalidateChildInsetsPadding(); }); - PointerOverElementProperty.Changed.AddClassHandler((topLevel, e) => - { - if (e.OldValue is InputElement oldInputElement) - { - oldInputElement.PropertyChanged -= topLevel.PointerOverElementOnPropertyChanged; - } - - if (e.NewValue is InputElement newInputElement) - { - topLevel.SetCursor(newInputElement.Cursor); - newInputElement.PropertyChanged += topLevel.PointerOverElementOnPropertyChanged; - } - }); ToolTip.ServiceEnabledProperty.Changed.Subscribe(OnToolTipServiceEnabledChanged); } @@ -209,15 +184,20 @@ namespace Avalonia.Controls /// /// The dependency resolver to use. If null the default dependency resolver will be used. /// - public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver) + internal TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver) { PlatformImpl = impl ?? throw new InvalidOperationException( "Could not create window implementation: maybe no windowing subsystem was initialized?"); + dependencyResolver ??= AvaloniaLocator.Current; + _source = new PresentationSource(this, impl, dependencyResolver, () => ClientSize); + _source.RootVisual = this; + _source.Renderer.SceneInvalidated += SceneInvalidated; _scaling = ValidateScaling(impl.RenderScaling); _actualTransparencyLevel = PlatformImpl.TransparencyLevel; + - dependencyResolver ??= AvaloniaLocator.Current; + _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); @@ -226,13 +206,10 @@ namespace Avalonia.Controls _globalStyles = TryGetService(dependencyResolver); _applicationThemeHost = TryGetService(dependencyResolver); - Renderer = new CompositingRenderer(this, impl.Compositor, () => PlatformImpl.Surfaces ?? []); - Renderer.SceneInvalidated += SceneInvalidated; - impl.SetInputRoot(this); + impl.Closed = HandleClosed; - impl.Input = HandleInput; impl.Paint = HandlePaint; impl.Resized = HandleResized; impl.ScalingChanged = HandleScalingChanged; @@ -276,8 +253,7 @@ namespace Avalonia.Controls impl.LostFocus += PlatformImpl_LostFocus; - _pointerOverPreProcessor = new PointerOverPreProcessor(this); - _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor); + if(impl.TryGetFeature() is {} systemNavigationManager) { @@ -407,26 +383,7 @@ namespace Avalonia.Controls remove => RemoveHandler(BackRequestedEvent, value); } - internal ILayoutManager LayoutManager - { - get - { - if (_layoutManager is null) - { - _layoutManager = CreateLayoutManager(); - - if (_layoutManager is LayoutManager typedLayoutManager) - { - _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager); - _layoutDiagnosticBridge.SetupBridge(); - } - } - - return _layoutManager; - } - } - - ILayoutManager ILayoutRoot.LayoutManager => LayoutManager; + internal ILayoutManager LayoutManager => _source.LayoutManager; /// /// Gets the platform-specific window implementation. @@ -461,24 +418,23 @@ namespace Avalonia.Controls /// /// Gets the renderer for the window. /// - internal CompositingRenderer Renderer { get; } - - internal IHitTester HitTester => HitTesterOverride ?? Renderer; + internal CompositingRenderer Renderer => _source.Renderer; // This property setter is here purely for lazy unit tests // that don't want to set up a proper hit-testable visual tree // and should be removed after fixing those tests - internal IHitTester? HitTesterOverride; - - IRenderer IRenderRoot.Renderer => Renderer; - IHitTester IRenderRoot.HitTester => HitTester; + internal IHitTester? HitTesterOverride + { + get => _source.HitTesterOverride; + set => _source.HitTesterOverride = value; + } /// /// Gets a value indicating whether the renderer should draw specific diagnostics. /// public RendererDiagnostics RendererDiagnostics => Renderer.Diagnostics; - internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition; + internal PixelPoint? LastPointerPosition => _source.GetLastPointerPosition(this); /// /// Gets the access key handler for the window. @@ -488,24 +444,7 @@ namespace Avalonia.Controls /// /// Gets or sets the keyboard navigation handler for the window. /// - IKeyboardNavigationHandler? IInputRoot.KeyboardNavigationHandler => _keyboardNavigationHandler; - - /// - IInputElement? IInputRoot.PointerOverElement - { - get => GetValue(PointerOverElementProperty); - set => SetValue(PointerOverElementProperty, value); - } - - /// - /// Gets or sets a value indicating whether access keys are shown in the window. - /// - bool IInputRoot.ShowAccessKeys - { - get => GetValue(AccessText.ShowAccessKeyProperty); - set => SetValue(AccessText.ShowAccessKeyProperty, value); - } - + /// /// Helper for setting the color of the platform's system bars. /// @@ -545,8 +484,6 @@ namespace Avalonia.Controls return control.GetValue(AutoSafeAreaPaddingProperty); } - double ILayoutRoot.LayoutScaling => _scaling; - /// public double RenderScaling => _scaling; @@ -575,23 +512,22 @@ namespace Avalonia.Controls /// public IClipboard? Clipboard => PlatformImpl?.TryGetFeature(); - /// - public IFocusManager? FocusManager => _focusManager ??= new FocusManager(this); - - /// - public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService(); - - /// - Point IRenderRoot.PointToClient(PixelPoint p) - { - return PlatformImpl?.PointToClient(p) ?? default; - } + /// + /// Gets focus manager of the root. + /// + /// + /// Focus manager can be null only if window wasn't initialized yet. + /// + public IFocusManager FocusManager => _source.FocusManager; - /// - PixelPoint IRenderRoot.PointToScreen(Point p) - { - return PlatformImpl?.PointToScreen(p) ?? default; - } + /// + /// Represents a contract for accessing top-level platform-specific settings. + /// + /// + /// PlatformSettings can be null only if window wasn't initialized yet. + /// + // TODO: Un-private + private IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService(); /// /// Gets the for which the given is hosted in. @@ -600,7 +536,14 @@ namespace Avalonia.Controls /// The TopLevel public static TopLevel? GetTopLevel(Visual? visual) { - return visual?.VisualRoot as TopLevel; + while (visual != null) + { + if (visual is TopLevel tl) + return tl; + visual = visual.VisualParent; + } + + return null; } /// @@ -651,7 +594,6 @@ namespace Avalonia.Controls } private IDisposable? _insetsPaddings; - private FocusManager? _focusManager; private void InvalidateChildInsetsPadding() { @@ -677,11 +619,6 @@ namespace Avalonia.Controls } } - /// - /// Creates the layout manager for this . - /// - private protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); - /// /// Handles a paint notification from . /// @@ -691,18 +628,16 @@ namespace Avalonia.Controls Renderer.Paint(rect); } - protected void StartRendering() => MediaContext.Instance.AddTopLevel(this, LayoutManager, Renderer); + private protected void StartRendering() => MediaContext.Instance.AddTopLevel(this, LayoutManager, Renderer); - protected void StopRendering() => MediaContext.Instance.RemoveTopLevel(this); + private protected void StopRendering() => MediaContext.Instance.RemoveTopLevel(this); /// /// Handles a closed notification from . /// private protected virtual void HandleClosed() { - Renderer.SceneInvalidated -= SceneInvalidated; - // We need to wait for the renderer to complete any in-flight operations - Renderer.Dispose(); + _source.Dispose(); StopRendering(); Debug.Assert(PlatformImpl != null); @@ -720,18 +655,12 @@ namespace Avalonia.Controls _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; } - _layoutDiagnosticBridge?.Dispose(); - _layoutDiagnosticBridge = null; - - _pointerOverPreProcessor?.OnCompleted(); - _pointerOverPreProcessorSubscription?.Dispose(); _backGestureSubscription?.Dispose(); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); - var visualArgs = new VisualTreeAttachmentEventArgs(this, this); - OnDetachedFromVisualTreeCore(visualArgs); + _source.RootVisual = null!; OnClosed(EventArgs.Empty); @@ -784,15 +713,6 @@ namespace Avalonia.Controls ActualTransparencyLevel = transparencyLevel; } - /// - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - throw new InvalidOperationException( - $"Control '{GetType().Name}' is a top level control and cannot be added as a child."); - } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -844,73 +764,6 @@ namespace Avalonia.Controls return result; } - /// - /// Handles input from . - /// - /// The event args. - private void HandleInput(RawInputEventArgs e) - { - if (PlatformImpl != null) - { - Dispatcher.UIThread.Send(static state => - { - using var _ = Diagnostic.BeginLayoutInputPass(); - - var (topLevel, e) = (ValueTuple)state!; - if (e is RawPointerEventArgs pointerArgs) - { - var hitTestElement = topLevel.InputHitTest(pointerArgs.Position, enabledElementsOnly: false); - - pointerArgs.InputHitTestResult = (hitTestElement, FirstEnabledAncestor(hitTestElement)); - } - - topLevel._inputManager?.ProcessInput(e); - }, (this, 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; - } - - 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(); - } - - private void PointerOverElementOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) - { - if (e.Property == CursorProperty && sender is InputElement inputElement) - { - SetCursor(inputElement.Cursor); - } - } - private void GlobalActualThemeVariantChanged(object? sender, EventArgs e) { SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template); @@ -918,7 +771,6 @@ namespace Avalonia.Controls private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) { - _pointerOverPreProcessor?.SceneInvalidated(e.DirtyRect); UpdateToolTip(e.DirtyRect); } @@ -935,12 +787,12 @@ namespace Avalonia.Controls private void UpdateToolTip(Rect dirtyRect) { - if (_tooltipService != null && IsPointerOver && _pointerOverPreProcessor?.LastPosition is { } lastPos) + if (_tooltipService != null && IsPointerOver && _source.GetLastPointerPosition(this) is { } lastPos) { var clientPoint = this.PointToClient(lastPos); if (dirtyRect.Contains(clientPoint)) { - _tooltipService.Update(this, this.InputHitTest(clientPoint, enabledElementsOnly: false) as Visual); + _tooltipService.Update(_source, this.InputHitTest(clientPoint, enabledElementsOnly: false) as Visual); } } } @@ -964,8 +816,6 @@ namespace Avalonia.Controls // Do nothing becuase TopLevel should't apply MirrorTransform on himself. } - ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => PlatformImpl?.TryGetFeature(); - private double ValidateScaling(double scaling) { if (MathUtilities.IsNegativeOrNonFinite(scaling) || MathUtilities.IsZero(scaling)) @@ -983,49 +833,5 @@ namespace Avalonia.Controls return scaling; } - - /// - /// 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; - } - } } } diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 068ffbcb66..033be87c7f 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -168,7 +168,7 @@ namespace Avalonia.Controls item.IsExpanded = true; if (item.Presenter?.Panel is null) - (this.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); + this.GetLayoutManager()?.ExecuteLayoutPass(); if (item.Presenter?.Panel is { } panel) { diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index e883bb533b..7e79026c60 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -459,7 +459,7 @@ namespace Avalonia.Controls element.BringIntoView(); return element; } - else if (this.GetVisualRoot() is ILayoutRoot root) + else if (this.GetLayoutRoot() is {} root) { // Create and measure the element to be brought into view. Store it in a field so that // it can be re-used in the layout pass. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 65ac2897cc..accda02fc6 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -86,7 +86,7 @@ namespace Avalonia.Controls /// /// A top-level window. /// - public class Window : WindowBase, IFocusScope, ILayoutRoot + public class Window : WindowBase, IFocusScope { private static readonly Lazy s_defaultIcon = new(LoadDefaultIcon); private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index 9ac24eb85a..b1dd6122f6 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -169,7 +169,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface public AutomationPeer? GetAutomationPeer() { - return _inputRoot is Control c ? ControlAutomationPeer.CreatePeerForElement(c) : null; + return _inputRoot?.RootElement is Control c ? ControlAutomationPeer.CreatePeerForElement(c) : null; } public bool RawTextInputEvent(ulong timeStamp, string text) diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 9d3752110c..b9539c3fb7 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -6,6 +6,7 @@ using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Native.Interop; using Avalonia.Platform; +using Avalonia.Rendering; using MicroCom.Runtime; namespace Avalonia.Native @@ -136,8 +137,9 @@ namespace Avalonia.Native { if(e.Type == RawPointerEventType.LeftButtonDown) { - var window = _inputRoot as Window; - var visual = window?.Renderer.HitTestFirst(e.Position, window, x => + // TODO: Casts like this are evil + var source = (PresentationSource?)_inputRoot; + var visual = source?.Renderer.HitTestFirst(e.Position, source.RootElement, x => { if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) { diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 5339744378..91f90032f2 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -90,7 +90,7 @@ namespace Avalonia.OpenGL.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - _compositor = (this.GetVisualRoot()?.Renderer as IRendererWithCompositor)?.Compositor; + _compositor = (this.GetPresentationSource()?.Renderer as IRendererWithCompositor)?.Compositor; RequestNextFrameRendering(); } @@ -201,11 +201,11 @@ namespace Avalonia.OpenGL.Controls private void Update() { _updateQueued = false; - if (VisualRoot is not { } visualRoot) + if (this.GetPresentationSource() is not { } source) return; if(!EnsureInitialized()) return; - using (_resources.BeginDraw(GetPixelSize(visualRoot))) + using (_resources.BeginDraw(GetPixelSize(source))) OnOpenGlRender(_resources.Context.GlInterface, _resources.Fbo); } @@ -263,9 +263,9 @@ namespace Avalonia.OpenGL.Controls } } - private PixelSize GetPixelSize(IRenderRoot visualRoot) + private PixelSize GetPixelSize(IPresentationSource source) { - var scaling = visualRoot.RenderScaling; + var scaling = source.RenderScaling; return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)), Math.Max(1, (int)(Bounds.Height * scaling))); } diff --git a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs index 920c66ed1a..32a1a5c654 100644 --- a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs +++ b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; +using Avalonia.VisualTree; namespace Avalonia.Themes.Fluent.Accents; @@ -120,7 +121,7 @@ internal sealed class SystemAccentColors : ResourceProvider return owner switch { Application app => app.PlatformSettings, - Visual visual => TopLevel.GetTopLevel(visual)?.PlatformSettings, + Visual visual => visual.GetPlatformSettings(), _ => null }; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index c931b1a6fb..602381d7c9 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -267,7 +267,9 @@ namespace Avalonia.X11 ? DBusSystemDialog.TryCreateAsync(Handle) : Task.FromResult(null), () => GtkSystemDialog.TryCreate(this), - () => Task.FromResult(InputRoot is TopLevel tl + // TODO: This will be incompatible with "root element is not a TopLevel" scenarios, + // we should probably have a separate API for this + () => Task.FromResult(InputRoot.RootElement is TopLevel tl ? (IStorageProvider?)new ManagedStorageProvider(tl) : null) }); diff --git a/src/Avalonia.X11/X11WindowModes/XEmbedClientWindowMode.cs b/src/Avalonia.X11/X11WindowModes/XEmbedClientWindowMode.cs index 89ceec5813..824a26d10f 100644 --- a/src/Avalonia.X11/X11WindowModes/XEmbedClientWindowMode.cs +++ b/src/Avalonia.X11/X11WindowModes/XEmbedClientWindowMode.cs @@ -9,7 +9,7 @@ partial class X11Window { public class XEmbedClientWindowMode : X11WindowMode { - EmbeddableControlRoot? Root => Window._inputRoot as EmbeddableControlRoot; + EmbeddableControlRoot? Root => Window._inputRoot?.RootElement as EmbeddableControlRoot; private bool _focusedInEmbedder; private bool _embedderActivated; private bool _disabled; @@ -79,7 +79,7 @@ partial class X11Window if (args.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) { if (KeyboardDevice.Instance.FocusedElement is Visual visual - && visual.VisualRoot is EmbeddableControlRoot root + && TopLevel.GetTopLevel(visual) is EmbeddableControlRoot root && root.PlatformImpl is X11Window window && window._mode is XEmbedClientWindowMode xembedMode && xembedMode._currentEmbedder != IntPtr.Zero) diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 8c30867390..5d0a762b5c 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -209,7 +209,7 @@ namespace Avalonia.Win32 private Point GetDragLocation(UnmanagedMethods.POINT dragPoint) { var screenPt = new PixelPoint(dragPoint.X, dragPoint.Y); - return ((Visual)_target).PointToClient(screenPt); + return _target.RootElement.PointToClient(screenPt); } protected override void Destroyed() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 57542f7f95..6beffe1313 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -937,7 +937,7 @@ namespace Avalonia.Win32 return IntPtr.Zero; } case WindowsMessage.WM_GETOBJECT: - if ((long)lParam == uiaRootObjectId && UiaCoreTypesApi.IsNetComInteropAvailable && _owner is Control control) + if ((long)lParam == uiaRootObjectId && UiaCoreTypesApi.IsNetComInteropAvailable && _owner?.RootElement is Control control) { var peer = ControlAutomationPeer.CreatePeerForElement(control); var node = AutomationNode.GetOrCreate(peer); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index 4ee47c7b87..12a5c34253 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -224,7 +224,7 @@ namespace Avalonia.Win32 private HitTestValues HitTestVisual(IntPtr lParam) { var position = PointToClient(PointFromLParam(lParam)); - if (_owner is Window window) + if (_owner?.RootElement is {} window) { var visual = window.GetVisualAt(position, x => { diff --git a/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs index 5d86ce13ad..d11872ba6a 100644 --- a/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs @@ -12,21 +12,28 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Keypresses_Should_Be_Sent_To_Root_If_No_Focused_Element() { - var target = new KeyboardDevice(); - var root = new Mock(); - - target.ProcessRawEvent( - new RawKeyEventArgs( - target, - 0, - root.Object, - RawKeyEventType.KeyDown, - Key.A, - RawInputModifiers.None, - PhysicalKey.A, - "a")); - - root.Verify(x => x.RaiseEvent(It.IsAny())); + using (UnitTestApplication.Start(TestServices.FocusableWindow)) + { + var window = new Window(); + window.FocusManager.ClearFocus(); + int raised = 0; + window.KeyDown += (sender, ev) => + { + if (sender == window && ev.RoutedEvent == InputElement.KeyDownEvent) + raised++; + }; + KeyboardDevice.Instance!.ProcessRawEvent( + new RawKeyEventArgs( + KeyboardDevice.Instance, + 0, + window.GetInputRoot()!, + RawKeyEventType.KeyDown, + Key.A, + RawInputModifiers.None, + PhysicalKey.A, + "a")); + Assert.Equal(1, raised); + } } [Fact] @@ -61,17 +68,24 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void TextInput_Should_Be_Sent_To_Root_If_No_Focused_Element() { - var target = new KeyboardDevice(); - var root = new Mock(); - - target.ProcessRawEvent( - new RawTextInputEventArgs( - target, - 0, - root.Object, - "Foo")); - - root.Verify(x => x.RaiseEvent(It.IsAny())); + using (UnitTestApplication.Start(TestServices.FocusableWindow)) + { + var window = new Window(); + window.FocusManager.ClearFocus(); + int raised = 0; + window.TextInput += (sender, ev) => + { + if (sender == window && ev.RoutedEvent == InputElement.TextInputEvent) + raised++; + }; + KeyboardDevice.Instance!.ProcessRawEvent( + new RawTextInputEventArgs( + KeyboardDevice.Instance, + 0, + window.GetInputRoot()!, + "Foo")); + Assert.Equal(1, raised); + } } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs index e2789f9d2e..6ae9008705 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs @@ -246,12 +246,12 @@ namespace Avalonia.Base.UnitTests.Input // Enter decorator SetHit(renderer, decorator); - SetMove(deviceMock, root, decorator); + SetMove(deviceMock, root.InputRoot, decorator); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); // Leave decorator SetHit(renderer, canvas); - SetMove(deviceMock, root, canvas); + SetMove(deviceMock, root.InputRoot, canvas); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); Assert.Equal( @@ -491,7 +491,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(canvas.IsPointerOver); // Send LeaveWindow. - impl.Object.Input!(new RawPointerEventArgs(deviceMock.Object, 0, root, RawPointerEventType.LeaveWindow, new Point(), default)); + impl.Object.Input!(new RawPointerEventArgs(deviceMock.Object, 0, root.InputRoot, RawPointerEventType.LeaveWindow, new Point(), default)); Assert.False(canvas.IsPointerOver); Assert.Equal( diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs index a12e5015fd..c75ff56d82 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs @@ -78,26 +78,26 @@ public abstract class PointerTestsBase : ScopedTestBase protected static RawPointerEventArgs CreateRawPointerArgs( IPointerDevice pointerDevice, - IInputRoot root, + TopLevel root, RawPointerEventType type, Point? position = default) { - return new RawPointerEventArgs(pointerDevice, 0, root, type, position ?? default, default); + return new RawPointerEventArgs(pointerDevice, 0, root.PresentationSource, type, position ?? default, default); } protected static RawPointerEventArgs CreateRawPointerMovedArgs( IPointerDevice pointerDevice, - IInputRoot root, + TopLevel root, Point? position = null) { - return new RawPointerEventArgs(pointerDevice, 0, root, RawPointerEventType.Move, + return new RawPointerEventArgs(pointerDevice, 0, root.PresentationSource, RawPointerEventType.Move, position ?? default, default); } protected static PointerEventArgs CreatePointerMovedArgs( IInputRoot root, IInputElement? source, Point? position = null) { - return new PointerEventArgs(InputElement.PointerMovedEvent, source, new Mock().Object, (Visual)root, + return new PointerEventArgs(InputElement.PointerMovedEvent, source, new Mock().Object, root.RootElement, position ?? default, default, PointerPointProperties.None, KeyModifiers.None); } diff --git a/tests/Avalonia.Base.UnitTests/VisualTests.cs b/tests/Avalonia.Base.UnitTests/VisualTests.cs index 1a196f950a..24deb24938 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTests.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTests.cs @@ -73,18 +73,19 @@ namespace Avalonia.Base.UnitTests var root = new TestRoot(); var called1 = false; var called2 = false; - + child1.AttachedToVisualTree += (s, e) => { - Assert.Equal(e.Parent, root); - Assert.Equal(e.Root, root); + // TODO: Tests are running against TestRoot, so behavior DOES NOT match the actual TopLevel. + Assert.Equal(e.AttachmentPoint, root); + Assert.Equal(e.RootVisual, root); called1 = true; }; child2.AttachedToVisualTree += (s, e) => { - Assert.Equal(e.Parent, root); - Assert.Equal(e.Root, root); + Assert.Equal(e.AttachmentPoint, root); + Assert.Equal(e.RootVisual, root); called2 = true; }; @@ -107,15 +108,16 @@ namespace Avalonia.Base.UnitTests child1.DetachedFromVisualTree += (s, e) => { - Assert.Equal(e.Parent, root); - Assert.Equal(e.Root, root); + // TODO: Tests are running against TestRoot, so behavior DOES NOT match the actual TopLevel. + Assert.Equal(e.AttachmentPoint, root); + Assert.Equal(e.RootVisual, root); called1 = true; }; child2.DetachedFromVisualTree += (s, e) => { - Assert.Equal(e.Parent, root); - Assert.Equal(e.Root, root); + Assert.Equal(e.AttachmentPoint, root); + Assert.Equal(e.RootVisual, root); called2 = true; }; diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 661791a6e4..20065bac60 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -759,18 +759,9 @@ namespace Avalonia.Controls.UnitTests } } - private class TestTopLevel : TopLevel + private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl) { - private readonly ILayoutManager _layoutManager; - public bool IsClosed { get; private set; } - public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null) - : base(impl) - { - _layoutManager = layoutManager ?? new LayoutManager(this); - } - - private protected override ILayoutManager CreateLayoutManager() => _layoutManager; } } } diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs index 85ba254b5d..ab93686966 100644 --- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs @@ -336,7 +336,7 @@ namespace Avalonia.Controls.UnitTests private static void Layout(Carousel target) { - ((ILayoutRoot)target.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass(); + target.GetLayoutManager()?.ExecuteLayoutPass(); } private static IControlTemplate CarouselTemplate() diff --git a/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs b/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs index 002fa567c6..bdaa8d6a98 100644 --- a/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs +++ b/tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls.UnitTests new RawKeyEventArgs( keyboardDevice, 0, - root, + root.InputRoot, RawKeyEventType.KeyDown, Key.F, RawInputModifiers.Control, diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 85d366953f..b4752c5186 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -1218,7 +1218,7 @@ namespace Avalonia.Controls.UnitTests private static void Layout(Control c) { - (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); + c.GetLayoutManager()?.ExecuteLayoutPass(); } private static ContentPresenter GetContainer(ItemsControl target, int index = 0) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index c76369ef0b..2fa6a47d27 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -642,7 +642,7 @@ namespace Avalonia.Controls.UnitTests private static void Layout(Control c) { - ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass(); + c.GetLayoutManager()?.ExecuteLayoutPass(); } private class Item diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index 4ff22cf402..cddac813f8 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -1002,17 +1002,8 @@ namespace Avalonia.Controls.UnitTests } } - private class TestTopLevel : TopLevel + private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl) { - private readonly ILayoutManager _layoutManager; - - public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null) - : base(impl) - { - _layoutManager = layoutManager ?? new LayoutManager(this); - } - - private protected override ILayoutManager CreateLayoutManager() => _layoutManager; } private static Mock CreateMockTopLevelImpl() diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index c32e97ed4a..023bad4f58 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -57,7 +57,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var parentMock = new Mock(); parentMock.As(); - parentMock.As(); + parentMock.As(); parentMock.As(); (target as ISetLogicalParent).SetParent(parentMock.Object); @@ -102,7 +102,7 @@ namespace Avalonia.Controls.UnitTests.Presenters }; var parentMock = new Mock(); - parentMock.As(); + parentMock.As(); parentMock.As(); parentMock.As().SetupGet(l => l.IsAttachedToLogicalTree).Returns(true); @@ -148,7 +148,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var parentMock = new Mock(); parentMock.As(); - parentMock.As(); + parentMock.As(); parentMock.As(); (target as ISetLogicalParent).SetParent(parentMock.Object); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 134b1e9c5b..cf2836da50 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -657,13 +657,22 @@ namespace Avalonia.Controls.UnitTests.Primitives button.Focus(); - var inputRoot = Assert.IsAssignableFrom(popup.Host); + var inputRoot = ((Visual)popup.Host!).GetInputRoot(); - var focusManager = inputRoot.FocusManager!; + var focusManager = inputRoot!.FocusManager!; Assert.Same(button, focusManager.GetFocusedElement()); //Ensure focus remains in the popup - inputRoot.KeyboardNavigationHandler!.Move(focusManager.GetFocusedElement()!, NavigationDirection.Next); +#pragma warning disable CS0618 // Type or member is obsolete + var handler = popup.Host switch + { + PopupRoot popupRoot => popupRoot.Tests_KeyboardNavigationHandler, + OverlayPopupHost overlayPopupHost => overlayPopupHost.Tests_KeyboardNavigationHandler, + _ => throw new InvalidOperationException("Unknown popup host type") + }; + + handler.Move(focusManager.GetFocusedElement()!, NavigationDirection.Next); +#pragma warning restore CS0618 // Type or member is obsolete Assert.Same(textBox, focusManager.GetFocusedElement()); popup.Close(); @@ -702,9 +711,9 @@ namespace Avalonia.Controls.UnitTests.Primitives button.Focus(); - var inputRoot = Assert.IsAssignableFrom(popup.Host); + var inputRoot = ((Visual)popup.Host!).GetInputRoot(); - var focusManager = inputRoot.FocusManager!; + var focusManager = inputRoot!.FocusManager!; Assert.Same(button, focusManager.GetFocusedElement()); border1.Child = null; @@ -1170,7 +1179,7 @@ namespace Avalonia.Controls.UnitTests.Primitives var ev = new PointerPressedEventArgs( popupContent, pointer, - (PopupRoot)popupContent.VisualRoot!, + (PopupRoot)TopLevel.GetTopLevel(popupContent)!, new Point(50 , 50), 0, new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 814bf00344..3c36b7ff74 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -2555,7 +2555,7 @@ namespace Avalonia.Controls.UnitTests.Primitives private static void Layout(Control c) { - ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass(); + c.GetLayoutManager()?.ExecuteLayoutPass(); } private static FuncControlTemplate Template() diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index f0f4fe05ae..2b9d7a940f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -1341,7 +1341,7 @@ namespace Avalonia.Controls.UnitTests.Primitives private static void Layout(Control c) { - (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); + c.GetLayoutManager()?.ExecuteLayoutPass(); } public static IDisposable Start() diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index a4cdf592cc..0e171474fe 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -883,18 +883,8 @@ namespace Avalonia.Controls.UnitTests }; } - private class TestTopLevel : TopLevel + private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl) { - private readonly ILayoutManager _layoutManager; - public bool IsClosed { get; private set; } - - public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null) - : base(impl) - { - _layoutManager = layoutManager ?? new LayoutManager(this); - } - - private protected override ILayoutManager CreateLayoutManager() => _layoutManager; } private static void Prepare(TabControl target) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 31b8fe45e8..fa578a7afe 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -2284,17 +2284,8 @@ namespace Avalonia.Controls.UnitTests } } - private class TestTopLevel : TopLevel + private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl) { - private readonly ILayoutManager _layoutManager; - - public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null) - : base(impl) - { - _layoutManager = layoutManager ?? new LayoutManager(this); - } - - private protected override ILayoutManager CreateLayoutManager() => _layoutManager; } private static Mock CreateMockTopLevelImpl() diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 321851a795..721876312c 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -38,8 +38,8 @@ namespace Avalonia.Controls.UnitTests { _toolTipOpenSubscription = ToolTip.IsOpenProperty.Changed.Subscribe(new AnonymousObserver>(e => { - if (e.Sender is Visual { VisualRoot: {} root } visual) - OverlayLayer.GetOverlayLayer(visual)!.Measure(root.ClientSize); + if (e.Sender is Visual visual && TopLevel.GetTopLevel(visual) is {} root) + OverlayLayer.GetOverlayLayer(visual)?.Measure(root.ClientSize); })); } @@ -524,7 +524,7 @@ namespace Avalonia.Controls.UnitTests AssertToolTipOpen(target); var topLevel = TopLevel.GetTopLevel(target); - topLevel!.PlatformImpl!.Input!(new RawPointerEventArgs(s_mouseDevice, (ulong)DateTime.Now.Ticks, topLevel, + topLevel!.PlatformImpl!.Input!(new RawPointerEventArgs(s_mouseDevice, (ulong)DateTime.Now.Ticks, topLevel.InputRoot, RawPointerEventType.LeaveWindow, default(RawPointerPoint), RawInputModifiers.None)); Assert.False(ToolTip.GetIsOpen(target)); @@ -577,7 +577,7 @@ namespace Avalonia.Controls.UnitTests hitTesterMock.Setup(m => m.HitTestFirst(point, window, It.IsAny>())) .Returns(control); - var root = (IInputRoot?)control?.VisualRoot ?? window; + var root = control?.GetInputRoot() ?? window.InputRoot; var timestamp = (ulong)DateTime.Now.Ticks; windowImpl.Object.Input!(new RawPointerEventArgs(s_mouseDevice, timestamp, root, @@ -585,7 +585,7 @@ namespace Avalonia.Controls.UnitTests if (lastRoot != null && lastRoot != root) { - ((TopLevel)lastRoot).PlatformImpl?.Input!(new RawPointerEventArgs(s_mouseDevice, timestamp, + ((TopLevel)lastRoot.RootElement)?.PlatformImpl?.Input?.Invoke(new RawPointerEventArgs(s_mouseDevice, timestamp, lastRoot, RawPointerEventType.LeaveWindow, new Point(-1,-1), RawInputModifiers.None)); } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 410b09a8b3..9614ab8df9 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -82,11 +82,11 @@ namespace Avalonia.Controls.UnitTests { var impl = CreateMockTopLevelImpl(); - var target = new TestTopLevel(impl.Object, Mock.Of()); - + var target = new TestTopLevel(impl.Object); + // The layout pass should be scheduled by the derived class. - var layoutManagerMock = Mock.Get(target.LayoutManager); - layoutManagerMock.Verify(x => x.ExecuteLayoutPass(), Times.Never); + Assert.Equal(0, target.Measured); + Assert.Equal(0, target.Arranged); } } @@ -208,7 +208,7 @@ namespace Avalonia.Controls.UnitTests var input = new RawKeyEventArgs( new Mock().Object, 0, - target, + target.InputRoot, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.None, @@ -222,7 +222,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Adding_Top_Level_As_Child_Should_Throw_Exception() + public void Adding_Top_Level_As_Child_Should_Not_Exception() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -234,7 +234,7 @@ namespace Avalonia.Controls.UnitTests target.Content = child; target.ApplyTemplate(); - Assert.Throws(() => target.Presenter!.ApplyTemplate()); + target.Presenter!.ApplyTemplate(); } } @@ -254,22 +254,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Close_Should_Dispose_LayoutManager() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var impl = CreateMockTopLevelImpl(true); - - var layoutManager = new Mock(); - var target = new TestTopLevel(impl.Object, layoutManager.Object); - - impl.Object.Closed!(); - - layoutManager.Verify(x => x.Dispose()); - } - } - [Fact] public void Reacts_To_Changes_In_Global_Styles() { @@ -329,18 +313,20 @@ namespace Avalonia.Controls.UnitTests return topLevel; } - private class TestTopLevel : TopLevel + private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl) { - private readonly ILayoutManager _layoutManager; - public bool IsClosed { get; private set; } - - public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null) - : base(impl) + public int Measured, Arranged; + protected override Size MeasureCore(Size availableSize) { - _layoutManager = layoutManager ?? new LayoutManager(this); + Measured++; + return base.MeasureCore(availableSize); } - private protected override ILayoutManager CreateLayoutManager() => _layoutManager; + protected override void ArrangeCore(Rect finalRect) + { + Arranged++; + base.ArrangeCore(finalRect); + } } } } diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index 8db2702e35..b84fef1fb2 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -377,7 +377,7 @@ namespace Avalonia.Controls.UnitTests private void Layout(Control c) { - (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); + c.GetLayoutManager()?.ExecuteLayoutPass(); } private class TestTransition : IPageTransition diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index bf35bc117d..1d678fa4f4 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1781,7 +1781,7 @@ namespace Avalonia.Controls.UnitTests private void Layout(Control c) { - (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); + c.GetLayoutManager()?.ExecuteLayoutPass(); } private void ClickContainer(Control container, KeyModifiers modifiers) diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 3611e06e48..2645280413 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -85,7 +85,7 @@ namespace Avalonia.Controls.UnitTests.Utils target.ProcessRawEvent(new RawKeyEventArgs(target, 0, - root, + root.InputRoot, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.Control, @@ -129,7 +129,7 @@ namespace Avalonia.Controls.UnitTests.Utils target.ProcessRawEvent(new RawKeyEventArgs(target, 0, - root, + root.InputRoot, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.Control, @@ -173,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Utils target.ProcessRawEvent(new RawKeyEventArgs(target, 0, - root, + root.InputRoot, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.Control, @@ -184,7 +184,7 @@ namespace Avalonia.Controls.UnitTests.Utils target.ProcessRawEvent(new RawKeyEventArgs(target, 0, - root, + root.InputRoot, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.Control, @@ -235,7 +235,7 @@ namespace Avalonia.Controls.UnitTests.Utils target.ProcessRawEvent(new RawKeyEventArgs(target, 0, - root, + root.InputRoot, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.Control, @@ -246,7 +246,7 @@ namespace Avalonia.Controls.UnitTests.Utils target.ProcessRawEvent(new RawKeyEventArgs(target, 0, - root, + root.InputRoot, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.Control, diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs index 4c0ec8436a..cc506dd7a9 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs @@ -344,6 +344,6 @@ namespace Avalonia.Controls.UnitTests }); } - private static void Layout(Control c) => ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass(); + private static void Layout(Control c) => c.GetLayoutManager()?.ExecuteLayoutPass(); } } diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index 18c53efae7..0643e0758f 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs @@ -2352,8 +2352,7 @@ namespace Avalonia.Controls.UnitTests private static void Layout(Control target) { - var root = (ILayoutRoot?)target.GetVisualRoot(); - root?.LayoutManager.ExecuteLayoutPass(); + target.GetLayoutManager()?.ExecuteLayoutPass(); } private static IControlTemplate ListBoxItemTemplate() diff --git a/tests/Avalonia.RenderTests/TestRenderRoot.cs b/tests/Avalonia.RenderTests/TestRenderRoot.cs index ad2fa7afc0..0c87ed0935 100644 --- a/tests/Avalonia.RenderTests/TestRenderRoot.cs +++ b/tests/Avalonia.RenderTests/TestRenderRoot.cs @@ -3,24 +3,41 @@ using System.Threading.Tasks; using System; using System.Collections.Generic; using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Input.TextInput; +using Avalonia.Layout; using Avalonia.Platform; +using Avalonia.Rendering.Composition; namespace Avalonia.Skia.RenderTests { - public class TestRenderRoot : Decorator, IRenderRoot + public class TestRenderRoot : Decorator, IPresentationSource, IInputRoot, ILayoutRoot { private readonly IRenderTarget _renderTarget; public Size ClientSize { get; private set; } - internal IRenderer Renderer { get; private set; } = null!; - IRenderer IRenderRoot.Renderer => Renderer; - IHitTester IRenderRoot.HitTester => new NullHitTester(); + internal CompositingRenderer Renderer { get; private set; } = null!; + IRenderer IPresentationSource.Renderer => Renderer; + IHitTester IPresentationSource.HitTester => new NullHitTester(); + public IInputRoot InputRoot => this; + + ILayoutRoot IPresentationSource.LayoutRoot => this; + + public double LayoutScaling => 1d; + + public ILayoutManager LayoutManager { get; } + + Layoutable ILayoutRoot.RootVisual => this; + + public Visual? RootVisual => this; public double RenderScaling { get; } public TestRenderRoot(double scaling, IRenderTarget renderTarget) { _renderTarget = renderTarget; RenderScaling = scaling; + LayoutManager = new LayoutManager(this); + } class NullHitTester : IHitTester @@ -30,9 +47,11 @@ namespace Avalonia.Skia.RenderTests public Visual? HitTestFirst(Point p, Visual root, Func? filter) => null; } - internal void Initialize(IRenderer renderer, Control child) + internal void Initialize(CompositingRenderer renderer, Control child) { Renderer = renderer; + SetPresentationSourceForRootVisual(this); + Renderer.CompositionTarget.Root = this.CompositionVisual; Child = child; Width = child.Width; Height = child.Height; @@ -50,5 +69,12 @@ namespace Avalonia.Skia.RenderTests public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling); public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling); + + public IFocusManager? FocusManager { get; } + public IPlatformSettings? PlatformSettings { get; } + public IInputElement? PointerOverElement { get; set; } + public ITextInputMethodImpl? InputMethod { get; } + public InputElement RootElement => this; + } } diff --git a/tests/Avalonia.UnitTests/NullRenderer.cs b/tests/Avalonia.UnitTests/NullRenderer.cs index a001ec0d6e..3cd6ec627e 100644 --- a/tests/Avalonia.UnitTests/NullRenderer.cs +++ b/tests/Avalonia.UnitTests/NullRenderer.cs @@ -8,7 +8,7 @@ using Avalonia.Rendering; namespace Avalonia.UnitTests; -public sealed class NullRenderer : IRenderer +internal sealed class NullRenderer : IRenderer { public RendererDiagnostics Diagnostics { get; } = new(); diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 2e5a05f47d..0237cdbdce 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Input.TextInput; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Platform; @@ -14,7 +15,7 @@ using Moq; namespace Avalonia.UnitTests { - public class TestRoot : Decorator, IFocusScope, ILayoutRoot, IInputRoot, IRenderRoot, IStyleHost, ILogicalRoot + public class TestRoot : Decorator, IFocusScope, ILayoutRoot, IStyleHost, ILogicalRoot, IPresentationSource, IInputRoot { private readonly NameScope _nameScope = new NameScope(); private FocusManager? _focusManager; @@ -26,6 +27,7 @@ namespace Avalonia.UnitTests LayoutManager = new LayoutManager(this); IsVisible = true; KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); + SetPresentationSourceForRootVisual(this); } class NullHitTester : IHitTester @@ -56,21 +58,29 @@ namespace Avalonia.UnitTests public double LayoutScaling { get; set; } = 1; internal ILayoutManager LayoutManager { get; set; } + + ILayoutRoot IPresentationSource.LayoutRoot => this; + + Layoutable ILayoutRoot.RootVisual => RootElement; + ILayoutManager ILayoutRoot.LayoutManager => LayoutManager; + public Visual? RootVisual => this; public double RenderScaling => 1; internal IRenderer Renderer { get; set; } internal IHitTester HitTester { get; set; } - IRenderer IRenderRoot.Renderer => Renderer; - IHitTester IRenderRoot.HitTester => HitTester; + public IInputRoot InputRoot => this; + IRenderer IPresentationSource.Renderer => Renderer; + IHitTester IPresentationSource.HitTester => HitTester; - public IKeyboardNavigationHandler? KeyboardNavigationHandler => null; public IFocusManager FocusManager => _focusManager ??= new FocusManager(this); public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService(); public IInputElement? PointerOverElement { get; set; } - + public ITextInputMethodImpl? InputMethod { get; } + public InputElement RootElement => this; + public bool ShowAccessKeys { get; set; } public IStyleHost? StylingParent { get; set; } @@ -102,6 +112,7 @@ namespace Avalonia.UnitTests public Point PointToClient(PixelPoint p) => p.ToPoint(1); public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); + public void RegisterChildrenNames() { diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 9d475c06ca..b7e4eef1b9 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -112,7 +112,7 @@ namespace Avalonia.UnitTests internal IGlobalClock? GlobalClock { get; set; } internal IAccessKeyHandler? AccessKeyHandler { get; } public Func? KeyboardDevice { get; } - public Func? KeyboardNavigation { get; } + internal Func? KeyboardNavigation { get; } public Func? MouseDevice { get; } public IRuntimePlatform? Platform { get; } public IPlatformRenderInterface? RenderInterface { get; }