Browse Source

Introduce PresentationSource, move some responsibilities from TopLevel (#20624)

* Extracted IInputRoot out of TopLevel

* Move some input handling out of TopLevel

* Remove old class, make layout manager private

* Removed IRenderRoot

* Make VisualTreeAttachmentEventArgs a bit more sensible

* Move ILayoutRoot to PresentationSource

# Conflicts:
#	tests/Avalonia.Controls.UnitTests/TabControlTests.cs

* Updated some VisualRoot / GetVisualRoot usages

* Updated more XxxRoot usages

* More Root usages

* Addressed review

* Hurr-durr xml

* More fixes

* Maybe fix android compilation

* API diff

* Yet another cast

* I had to use MSIL analysis to detect those casts

* Fixed automation

* Fix PointerOverPreProcessor

* Fix?

* Removed yet another cast to Visual

* The amount of  random downcasts is astonishing

* Maybe fix mac

* Addressed review
pull/20730/head
Nikita Tsukanov 1 month ago
committed by GitHub
parent
commit
fa17c22131
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 410
      api/Avalonia.nupkg.xml
  2. 4
      samples/ControlCatalog/MainView.xaml.cs
  3. 2
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  4. 2
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  5. 6
      samples/GpuInterop/DrawingSurfaceDemoBase.cs
  6. 3
      src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
  7. 26
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  8. 4
      src/Avalonia.Base/Input/DragDropDevice.cs
  9. 8
      src/Avalonia.Base/Input/FocusManager.cs
  10. 5
      src/Avalonia.Base/Input/Gestures.cs
  11. 2
      src/Avalonia.Base/Input/IAccessKeyHandler.cs
  12. 29
      src/Avalonia.Base/Input/IInputRoot.cs
  13. 4
      src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
  14. 4
      src/Avalonia.Base/Input/InputElement.cs
  15. 8
      src/Avalonia.Base/Input/KeyboardDevice.cs
  16. 9
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  17. 20
      src/Avalonia.Base/Input/MouseDevice.cs
  18. 9
      src/Avalonia.Base/Input/Navigation/XYFocus.FindElements.cs
  19. 4
      src/Avalonia.Base/Input/Navigation/XYFocus.Impl.cs
  20. 12
      src/Avalonia.Base/Input/PenDevice.cs
  21. 4
      src/Avalonia.Base/Input/Pointer.cs
  22. 20
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  23. 6
      src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
  24. 2
      src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
  25. 10
      src/Avalonia.Base/Input/TouchDevice.cs
  26. 10
      src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs
  27. 1
      src/Avalonia.Base/Layout/ILayoutManager.cs
  28. 14
      src/Avalonia.Base/Layout/ILayoutRoot.cs
  29. 3
      src/Avalonia.Base/Layout/LayoutHelper.cs
  30. 25
      src/Avalonia.Base/Layout/LayoutManager.cs
  31. 21
      src/Avalonia.Base/Layout/Layoutable.cs
  32. 9
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  33. 2
      src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
  34. 46
      src/Avalonia.Base/Rendering/IPresentationSource.cs
  35. 42
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  36. 4
      src/Avalonia.Base/Rendering/IRenderer.cs
  37. 10
      src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
  38. 71
      src/Avalonia.Base/Visual.cs
  39. 14
      src/Avalonia.Base/VisualExtensions.cs
  40. 42
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  41. 32
      src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs
  42. 8
      src/Avalonia.Controls/Button.cs
  43. 4
      src/Avalonia.Controls/Chrome/TitleBar.cs
  44. 2
      src/Avalonia.Controls/Control.cs
  45. 3
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  46. 3
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  47. 29
      src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
  48. 2
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  49. 2
      src/Avalonia.Controls/Grid.cs
  50. 3
      src/Avalonia.Controls/GridSplitter.cs
  51. 2
      src/Avalonia.Controls/IMenu.cs
  52. 2
      src/Avalonia.Controls/ListBox.cs
  53. 3
      src/Avalonia.Controls/MaskedTextBox.cs
  54. 2
      src/Avalonia.Controls/Menu.cs
  55. 2
      src/Avalonia.Controls/MenuBase.cs
  56. 2
      src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
  57. 7
      src/Avalonia.Controls/NativeControlHost.cs
  58. 4
      src/Avalonia.Controls/NativeMenuBar.cs
  59. 4
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  60. 37
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  61. 46
      src/Avalonia.Controls/PresentationSource/PresentationSource.Cursor.cs
  62. 60
      src/Avalonia.Controls/PresentationSource/PresentationSource.Input.cs
  63. 69
      src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs
  64. 29
      src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs
  65. 116
      src/Avalonia.Controls/PresentationSource/PresentationSource.cs
  66. 4
      src/Avalonia.Controls/Primitives/AccessText.cs
  67. 3
      src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs
  68. 36
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  69. 2
      src/Avalonia.Controls/Primitives/Popup.cs
  70. 2
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  71. 1
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  72. 6
      src/Avalonia.Controls/RadioButton.cs
  73. 4
      src/Avalonia.Controls/RadioButtonGroupManager.cs
  74. 2
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  75. 6
      src/Avalonia.Controls/ToolTipService.cs
  76. 296
      src/Avalonia.Controls/TopLevel.cs
  77. 2
      src/Avalonia.Controls/TreeView.cs
  78. 2
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  79. 2
      src/Avalonia.Controls/Window.cs
  80. 2
      src/Avalonia.Native/TopLevelImpl.cs
  81. 6
      src/Avalonia.Native/WindowImpl.cs
  82. 10
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  83. 3
      src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs
  84. 4
      src/Avalonia.X11/X11Window.cs
  85. 4
      src/Avalonia.X11/X11WindowModes/XEmbedClientWindowMode.cs
  86. 2
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  87. 2
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  88. 2
      src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
  89. 46
      tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs
  90. 6
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  91. 10
      tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
  92. 18
      tests/Avalonia.Base.UnitTests/VisualTests.cs
  93. 11
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  94. 2
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  95. 2
      tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs
  96. 2
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  97. 2
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  98. 11
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  99. 6
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  100. 21
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

410
api/Avalonia.nupkg.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
@ -109,6 +109,42 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Input.IKeyboardNavigationHandler</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Input.KeyboardNavigationHandler</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Input.TextInput.ITextInputMethodRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.IEmbeddedLayoutRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.ILayoutRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.LayoutManager</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
@ -151,6 +187,24 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.IHitTester</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.IRenderer</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.IRenderRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Styling.IStyleable</Target>
@ -391,6 +445,42 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Input.IKeyboardNavigationHandler</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Input.KeyboardNavigationHandler</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Input.TextInput.ITextInputMethodRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.IEmbeddedLayoutRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.ILayoutRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.LayoutManager</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
@ -433,6 +523,24 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.IHitTester</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.IRenderer</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.IRenderRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Styling.IStyleable</Target>
@ -685,6 +793,42 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_KeyboardNavigationHandler</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_PlatformSettings</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_PointerOverElement</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_ShowAccessKeys</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.set_PointerOverElement(Avalonia.Input.IInputElement)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.set_ShowAccessKeys(System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
@ -973,12 +1117,30 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Rendering.SceneInvalidatedEventArgs.#ctor(Avalonia.Rendering.IRenderRoot,Avalonia.Rect)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Rendering.SceneInvalidatedEventArgs.get_RenderRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visual.get_VisualRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target>
@ -1003,6 +1165,24 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.VisualTree.VisualExtensions.GetVisualRoot(Avalonia.Visual)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.VisualTreeAttachmentEventArgs.#ctor(Avalonia.Visual,Avalonia.Rendering.IRenderRoot)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.VisualTreeAttachmentEventArgs.get_Root</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.ContextMenu.PlacementModeProperty</Target>
@ -1063,6 +1243,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TopLevel.PointerOverElementProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AppBuilder.get_LifetimeOverride</Target>
@ -1333,6 +1519,30 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.#ctor(Avalonia.Platform.ITopLevelImpl,Avalonia.IAvaloniaDependencyResolver)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.get_PlatformSettings</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.StartRendering</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.StopRendering</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TreeView.get_ItemContainerGenerator</Target>
@ -1603,6 +1813,42 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_KeyboardNavigationHandler</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_PlatformSettings</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_PointerOverElement</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.get_ShowAccessKeys</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.set_PointerOverElement(Avalonia.Input.IInputElement)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IInputRoot.set_ShowAccessKeys(System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
@ -1891,12 +2137,30 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Rendering.SceneInvalidatedEventArgs.#ctor(Avalonia.Rendering.IRenderRoot,Avalonia.Rect)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Rendering.SceneInvalidatedEventArgs.get_RenderRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.WriteResources(System.IO.Stream,System.Collections.Generic.List{System.ValueTuple{System.String,System.Int32,System.Func{System.IO.Stream}}})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visual.get_VisualRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target>
@ -1921,6 +2185,24 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.VisualTree.VisualExtensions.GetVisualRoot(Avalonia.Visual)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.VisualTreeAttachmentEventArgs.#ctor(Avalonia.Visual,Avalonia.Rendering.IRenderRoot)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.VisualTreeAttachmentEventArgs.get_Root</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.ContextMenu.PlacementModeProperty</Target>
@ -1981,6 +2263,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TopLevel.PointerOverElementProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AppBuilder.get_LifetimeOverride</Target>
@ -2251,6 +2539,30 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.#ctor(Avalonia.Platform.ITopLevelImpl,Avalonia.IAvaloniaDependencyResolver)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.get_PlatformSettings</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.StartRendering</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TopLevel.StopRendering</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TreeView.get_ItemContainerGenerator</Target>
@ -2839,6 +3151,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Input.IInputRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Media.ImmediateDrawingContext</Target>
@ -2881,6 +3199,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Embedding.EmbeddableControlRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase</Target>
@ -2893,6 +3217,36 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.OverlayPopupHost</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.PopupRoot</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.TopLevel</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Window</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.WindowBase</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Platform.IPopupImpl</Target>
@ -2917,6 +3271,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Dialogs.AboutAvaloniaDialog</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Dialogs.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Dialogs.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Metal.IMetalDevice</Target>
@ -2959,6 +3319,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Input.IInputRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Media.ImmediateDrawingContext</Target>
@ -3001,6 +3367,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Embedding.EmbeddableControlRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase</Target>
@ -3013,6 +3385,36 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.OverlayPopupHost</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.PopupRoot</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.TopLevel</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Window</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.WindowBase</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Platform.IPopupImpl</Target>
@ -3037,6 +3439,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Dialogs.AboutAvaloniaDialog</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Metal.IMetalDevice</Target>

4
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;

2
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;

2
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 });

6
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();

3
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;
}
}

26
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));
/// <summary>
/// Defines the ShowAccessKey attached property.
/// </summary>
public static readonly AttachedProperty<bool> ShowAccessKeyProperty =
AvaloniaProperty.RegisterAttached<AccessKeyHandler, Visual, bool>("ShowAccessKey", inherits: true);
/// <summary>
/// The registered access keys.
/// </summary>
@ -40,7 +47,7 @@ namespace Avalonia.Input
/// <summary>
/// The window to which the handler belongs.
/// </summary>
private IInputRoot? _owner;
private InputElement? _owner;
/// <summary>
/// Whether access keys are currently being shown;
@ -96,7 +103,7 @@ namespace Avalonia.Input
/// <remarks>
/// This method can only be called once, typically by the owner itself on creation.
/// </remarks>
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);
/// <summary>
/// Called when a key is pressed in the owner window.
/// </summary>
@ -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);
}
/// <summary>
@ -444,7 +454,7 @@ namespace Avalonia.Input
/// </summary>
/// <param name="owner">The owner to check.</param>
/// <returns>If focused element is decendant of owner <c>true</c>, otherwise <c>false</c>. </returns>
private static bool IsFocusWithinOwner(IInputRoot owner)
private static bool IsFocusWithinOwner(IInputElement owner)
{
var focusedElement = KeyboardDevice.Instance?.FocusedElement;
if (focusedElement is not InputElement inputElement)

4
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<Interactive>()?.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;

8
src/Avalonia.Base/Input/FocusManager.cs

@ -52,6 +52,11 @@ namespace Avalonia.Input
_contentRoot = contentRoot;
}
internal void SetContentRoot(IInputElement? contentRoot)
{
_contentRoot = contentRoot;
}
private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
private XYFocus _xyFocus = new();
@ -163,9 +168,10 @@ namespace Avalonia.Input
/// </summary>
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<IFocusManager>();
}

5
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));

2
src/Avalonia.Base/Input/IAccessKeyHandler.cs

@ -19,7 +19,7 @@ namespace Avalonia.Input
/// <remarks>
/// This method can only be called once, typically by the owner itself on creation.
/// </remarks>
void SetOwner(IInputRoot owner);
void SetOwner(InputElement owner);
/// <summary>
/// Registers an input element to be associated with an access key.

29
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
/// <summary>
/// Defines the interface for top-level input elements.
/// </summary>
[NotClientImplementable]
public interface IInputRoot : IInputElement
[PrivateApi]
public interface IInputRoot
{
/// <summary>
/// Gets or sets the keyboard navigation handler.
/// </summary>
IKeyboardNavigationHandler? KeyboardNavigationHandler { get; }
/// <summary>
/// Gets focus manager of the root.
/// </summary>
/// <remarks>
/// Focus manager can be null only if window wasn't initialized yet.
/// </remarks>
IFocusManager? FocusManager { get; }
/// <summary>
/// Represents a contract for accessing top-level platform-specific settings.
/// </summary>
/// <remarks>
/// PlatformSettings can be null only if window wasn't initialized yet.
/// </remarks>
IPlatformSettings? PlatformSettings { get; }
public IFocusManager? FocusManager { get; }
/// <summary>
/// Gets or sets the input element that the pointer is currently over.
/// </summary>
IInputElement? PointerOverElement { get; set; }
internal IInputElement? PointerOverElement { get; set; }
/// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>
bool ShowAccessKeys { get; set; }
internal ITextInputMethodImpl? InputMethod { get; }
internal InputElement RootElement { get; }
}
}

4
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.
/// </summary>
[Unstable]
public interface IKeyboardNavigationHandler
internal interface IKeyboardNavigationHandler
{
/// <summary>
/// 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.
/// </remarks>
[PrivateApi]
void SetOwner(IInputRoot owner);
void SetOwner(InputElement owner);
/// <summary>
/// Moves the focus in the specified direction.

4
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;

8
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)
{

9
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -10,13 +10,12 @@ namespace Avalonia.Input
/// <summary>
/// Handles keyboard navigation for a window.
/// </summary>
[Unstable]
public sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
internal sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
{
/// <summary>
/// The window to which the handler belongs.
/// </summary>
private IInputRoot? _owner;
private InputElement? _owner;
/// <summary>
/// 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.
/// </remarks>
[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)
{

20
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;

9
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();
// }
// TODO: The check for bounds is no longer correct
var root = (InputElement)element.GetVisualRoot()!;
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);

4
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);

12
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

4
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<IInputElement>();
}
private void OnCaptureDetached(object? sender, VisualTreeAttachmentEventArgs e)
{
Capture(GetNextCapture(e.Parent));
Capture(GetNextCapture(e.AttachmentPoint));
}

20
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);
}
}
}

6
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; }
}
}

2
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)
{

10
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);

10
src/Avalonia.Base/Layout/IEmbeddedLayoutRoot.cs

@ -1,10 +0,0 @@
namespace Avalonia.Layout
{
/// <summary>
/// A special layout root with enforced size for Arrange pass
/// </summary>
public interface IEmbeddedLayoutRoot : ILayoutRoot
{
Size AllocatedSize { get; }
}
}

1
src/Avalonia.Base/Layout/ILayoutManager.cs

@ -6,7 +6,6 @@ namespace Avalonia.Layout
/// <summary>
/// Manages measuring and arranging of controls.
/// </summary>
[PrivateApi]
public interface ILayoutManager : IDisposable
{
/// <summary>

14
src/Avalonia.Base/Layout/ILayoutRoot.cs

@ -5,22 +5,18 @@ namespace Avalonia.Layout
/// <summary>
/// Defines the root of a layoutable tree.
/// </summary>
[NotClientImplementable]
public interface ILayoutRoot
internal interface ILayoutRoot
{
/// <summary>
/// The size available to lay out the controls.
/// </summary>
Size ClientSize { get; }
/// <summary>
/// The scaling factor to use in layout.
/// </summary>
double LayoutScaling { get; }
public double LayoutScaling { get; }
/// <summary>
/// Associated instance of layout manager
/// </summary>
internal ILayoutManager LayoutManager { get; }
public ILayoutManager LayoutManager { get; }
public Layoutable RootVisual { get; }
}
}

3
src/Avalonia.Base/Layout/LayoutHelper.cs

@ -140,8 +140,7 @@ namespace Avalonia.Layout
/// </summary>
/// <param name="control">The control.</param>
/// <exception cref="Exception">Thrown when control has no root or returned layout scaling is invalid.</exception>
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;
/// <summary>
/// Rounds a size to integer values for layout purposes, compensating for high DPI screen

25
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
/// <summary>
/// Manages measuring and arranging of controls.
/// </summary>
[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<Layoutable> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid);
private readonly List<Layoutable> _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)
{

21
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.
/// </remarks>
public void UpdateLayout() => (this.GetVisualRoot() as ILayoutRoot)?.LayoutManager?.ExecuteLayoutPass();
public void UpdateLayout() => this.GetLayoutManager()?.ExecuteLayoutPass();
/// <summary>
/// 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;

9
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition;
/// </summary>
internal class CompositingRenderer : IRendererWithCompositor, IHitTester
{
private readonly IRenderRoot _root;
private readonly IPresentationSource _root;
private readonly Compositor _compositor;
private readonly RenderDataDrawingContext _recorder;
private readonly HashSet<Visual> _dirty = new();
@ -48,13 +48,12 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
/// <param name="surfaces">
/// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
/// </param>
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces)
public CompositingRenderer(IPresentationSource root, Compositor compositor, Func<IEnumerable<object>> 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()
{

2
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);
}
/// <summary>

46
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)
/// <summary>
/// Represents the host of the visual tree. On desktop platforms this is typically backed by a native window.
/// </summary>
[NotClientImplementable]
public interface IPresentationSource
{
/// <summary>
/// The current root of the visual tree
/// </summary>
public Visual? RootVisual { get; }
/// <summary>
/// The scaling factor to use in rendering.
/// </summary>
public double RenderScaling { get; }
internal IPlatformSettings? PlatformSettings { get; }
internal IRenderer Renderer { get; }
internal IHitTester HitTester { get; }
internal IInputRoot InputRoot { get; }
internal ILayoutRoot LayoutRoot { get; }
/// <summary>
/// Gets the client size of the window.
/// </summary>
internal Size ClientSize { get; }
internal PixelPoint PointToScreen(Point point);
internal Point PointToClient(PixelPoint point);
}

42
src/Avalonia.Base/Rendering/IRenderRoot.cs

@ -1,42 +0,0 @@
using Avalonia.Metadata;
namespace Avalonia.Rendering
{
/// <summary>
/// Represents the root of a renderable tree.
/// </summary>
[NotClientImplementable]
public interface IRenderRoot
{
/// <summary>
/// Gets the client size of the window.
/// </summary>
Size ClientSize { get; }
/// <summary>
/// Gets the renderer for the window.
/// </summary>
public IRenderer Renderer { get; }
public IHitTester HitTester { get; }
/// <summary>
/// The scaling factor to use in rendering.
/// </summary>
double RenderScaling { get; }
/// <summary>
/// Converts a point from screen to client coordinates.
/// </summary>
/// <param name="point">The point in screen device coordinates.</param>
/// <returns>The point in client coordinates.</returns>
Point PointToClient(PixelPoint point);
/// <summary>
/// Converts a point from client to screen coordinates.
/// </summary>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen device coordinates.</returns>
PixelPoint PointToScreen(Point point);
}
}

4
src/Avalonia.Base/Rendering/IRenderer.cs

@ -10,7 +10,7 @@ namespace Avalonia.Rendering
/// Defines the interface for a renderer.
/// </summary>
[PrivateApi]
public interface IRenderer : IDisposable
internal interface IRenderer : IDisposable
{
/// <summary>
/// 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
{
/// <summary>
/// Hit tests a location to find the visuals at the specified point.

10
src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs

@ -12,13 +12,9 @@ namespace Avalonia.Rendering
/// <summary>
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.
/// </summary>
/// <param name="root">The render root that has been updated.</param>
/// <param name="dirtyRect">The updated area.</param>
public SceneInvalidatedEventArgs(
IRenderRoot root,
Rect dirtyRect)
public SceneInvalidatedEventArgs(Rect dirtyRect)
{
RenderRoot = root;
DirtyRect = dirtyRect;
}
@ -27,9 +23,5 @@ namespace Avalonia.Rendering
/// </summary>
public Rect DirtyRect { get; }
/// <summary>
/// Gets the render root that has been invalidated.
/// </summary>
public IRenderRoot RenderRoot { get; }
}
}

71
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<Visual, EventArgs>? _affectsRenderWeakSubscriber;
@ -154,8 +156,6 @@ namespace Avalonia
/// </summary>
public Visual()
{
_visualRoot = this as IRenderRoot;
// Disable transitions until we're added to the visual tree.
DisableTransitions();
@ -339,7 +339,9 @@ namespace Avalonia
/// <summary>
/// Gets the root of the visual tree, if the control is attached to a visual tree.
/// </summary>
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
/// <summary>
/// Gets a value indicating whether this control is attached to a visual root.
/// </summary>
internal bool IsAttachedToVisualTree => VisualRoot != null;
internal bool IsAttachedToVisualTree => this.PresentationSource != null;
/// <summary>
/// Gets the control's parent visual.
@ -409,7 +411,7 @@ namespace Avalonia
/// </summary>
public void InvalidateVisual()
{
VisualRoot?.Renderer.AddDirty(this);
PresentationSource?.Renderer.AddDirty(this);
}
/// <summary>
@ -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);
}
/// <summary>
@ -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;
}
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);
}
@ -575,7 +576,6 @@ namespace Avalonia
{
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);
}
/// <summary>
@ -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<IRenderRoot>() ??
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);
}
}
}
}

14
src/Avalonia.Base/VisualExtensions.cs

@ -16,10 +16,11 @@ namespace Avalonia
/// <returns>The point in client coordinates.</returns>
public static Point PointToClient(this Visual visual, PixelPoint point)
{
var root = visual.VisualRoot ??
var source = visual.PresentationSource;
var root = source?.RootVisual ??
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 rootPoint = source.PointToClient(point);
return root.TranslatePoint(rootPoint, visual)!.Value;
}
/// <summary>
@ -30,10 +31,11 @@ namespace Avalonia
/// <returns>The point in screen coordinates.</returns>
public static PixelPoint PointToScreen(this Visual visual, Point point)
{
var root = visual.VisualRoot ??
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, (Visual)root);
return root.PointToScreen(p!.Value);
var p = visual.TranslatePoint(point, root);
return source.PointToScreen(p!.Value);
}
/// <summary>

42
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<Visual>();
}
return root.HitTester.HitTest(p, visual, filter);
return source.HitTester.HitTest(p, visual, filter);
}
/// <summary>
@ -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;
/// <summary>
/// Gets the root visual for an <see cref="Visual"/>.
/// Gets the layout manager for the visual's presentation source, or null if the visual is not attached to a visual root.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>
/// The root visual or null if the visual is not rooted.
/// </returns>
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;
}
/// <summary>
/// Attempts to obtain platform settings from the visual's root.
/// This will return null if the visual is not attached to a visual root.
/// </summary>
public static IPlatformSettings? GetPlatformSettings(this Visual visual) =>
visual.GetPresentationSource()?.PlatformSettings;
/// <summary>
/// Returns a value indicating whether this control is attached to a visual root.

32
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
/// <summary>
/// Initializes a new instance of the <see cref="VisualTreeAttachmentEventArgs"/> class.
/// </summary>
/// <param name="parent">The parent that the visual is being attached to or detached from.</param>
/// <param name="root">The root visual.</param>
public VisualTreeAttachmentEventArgs(Visual parent, IRenderRoot root)
/// <param name="attachmentPoint">The parent that the visual's tree is being attached to or detached from.</param>
/// <param name="presentationSource">Presentation source this visual is being attached to.</param>
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));
}
/// <summary>
/// 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
/// </summary>
public Visual Parent { get; }
public Visual? AttachmentPoint { get; }
[Obsolete("Use " + nameof(AttachmentPoint))]
public Visual? Parent => AttachmentPoint;
/// <summary>
/// Gets the root of the visual tree that the visual is being attached to or detached from.
/// </summary>
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;
/// <summary>
/// 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 <see cref="IPresentationSource.RootVisual"/>.
/// </summary>
public Visual RootVisual { get; set; }
}
}

8
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);
}

4
src/Avalonia.Controls/Chrome/TitleBar.cs

@ -54,7 +54,7 @@ namespace Avalonia.Controls.Chrome
_captionButtons = e.NameScope.Get<CaptionButtons>("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)
{

2
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)
{

3
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();

3
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();

29
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@ -16,8 +16,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>();
public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty =
AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial>(nameof(Material));
public static readonly StyledProperty<ExperimentalAcrylicMaterial?> MaterialProperty =
AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial?>(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;
var tl = TopLevel.GetTopLevel(this);
if (tl != null)
{
_subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
.Subscribe(x =>
{
if (tl.PlatformImpl is null)
if (tl.PlatformImpl is null || Material is null)
return;
if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None)
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
Material.PlatformTransparencyCompensationLevel =
tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
else if (x == WindowTransparencyLevel.Blur)
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
Material.PlatformTransparencyCompensationLevel =
tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
else if (x == WindowTransparencyLevel.AcrylicBlur)
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
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);
}
}

2
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

2
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;

3
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.

2
src/Avalonia.Controls/IMenu.cs

@ -23,6 +23,6 @@ namespace Avalonia.Controls
/// <summary>
/// Gets the root of the visual tree, if the control is attached to a visual tree.
/// </summary>
IRenderRoot? VisualRoot { get; }
TopLevel? TopLevel { get; }
}
}

2
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 &&

3
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<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));

2
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)
{

2
src/Avalonia.Controls/MenuBase.cs

@ -75,7 +75,7 @@ namespace Avalonia.Controls
/// <inheritdoc/>
IMenuInteractionHandler IMenu.InteractionHandler => InteractionHandler;
IRenderRoot? IMenu.VisualRoot => VisualRoot;
TopLevel? IMenu.TopLevel => TopLevel.GetTopLevel(this);
/// <inheritdoc/>
IMenuItem? IMenuElement.SelectedItem

2
src/Avalonia.Controls/MenuItemAccessKeyHandler.cs

@ -9,7 +9,7 @@ namespace Avalonia.Controls
/// </summary>
internal class MenuItemAccessKeyHandler : AccessKeyHandler
{
protected override void OnSetOwner(IInputRoot owner)
protected override void OnSetOwner(InputElement owner)
{
owner.AddHandler(InputElement.TextInputEvent, OnTextInput);
}

7
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
/// <inheritdoc />
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;

4
src/Avalonia.Controls/NativeMenuBar.cs

@ -34,7 +34,7 @@ namespace Avalonia.Controls
?? this.FindDescendantOfType<MenuBase>()
?? 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);
}

4
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)
{

37
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<TopLevel>().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

46
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();
}
/// <summary>
/// This should only be used by InProcessDragSource
/// </summary>
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);
}
}

60
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;
/// <summary>
/// Handles input from <see cref="ITopLevelImpl.Input"/>.
/// </summary>
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;
}
}

69
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;
}
/// <summary>
/// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes.
/// </summary>
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;
}
}
}

29
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<Size> _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;
}

116
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<Size> clientSizeProvider)
{
_clientSizeProvider = clientSizeProvider;
PlatformImpl = platformImpl;
_inputManager = TryGetService<IInputManager>(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<IPlatformSettings>();
ITextInputMethodImpl? IInputRoot.InputMethod => PlatformImpl?.TryGetFeature<ITextInputMethodImpl>();
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;
}
/// <summary>
/// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a
/// warning if not found.
/// </summary>
/// <typeparam name="T">The service type.</typeparam>
/// <param name="resolver">The resolver.</param>
/// <returns>The service.</returns>
private T? TryGetService<T>(IAvaloniaDependencyResolver resolver) where T : class
{
var result = resolver.GetService<T>();
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;
}
}

4
src/Avalonia.Controls/Primitives/AccessText.cs

@ -17,7 +17,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="ShowAccessKey"/> attached property.
/// </summary>
public static readonly AttachedProperty<bool> ShowAccessKeyProperty =
AvaloniaProperty.RegisterAttached<AccessText, Control, bool>("ShowAccessKey", inherits: true);
AccessKeyHandler.ShowAccessKeyProperty.AddOwner<AccessText>();
/// <summary>
/// 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))
{

3
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);

36
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
{
/// <summary>
/// Defines the <see cref="Transform"/> 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;
@ -60,38 +62,6 @@ namespace Avalonia.Controls.Primitives
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;
}
}
/// <inheritdoc />
internal override Interactive? InteractiveParent => Parent as Interactive;

2
src/Avalonia.Controls/Primitives/Popup.cs

@ -676,7 +676,7 @@ namespace Avalonia.Controls.Primitives
{
var newTarget = change.GetNewValue<Control?>() ?? this.FindLogicalAncestorOfType<Control>();
if (newTarget is null || newTarget.GetVisualRoot() != _openState.TopLevel)
if (newTarget is null || TopLevel.GetTopLevel(newTarget) != _openState.TopLevel)
{
Close();
return;

2
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");
}

1
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;

6
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);
}
}

4
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<IRenderRoot, RadioButtonGroupManager> s_registeredVisualRoots = new();
private static readonly ConditionalWeakTable<object, RadioButtonGroupManager> s_registeredVisualRoots = new();
private readonly Dictionary<string, List<WeakReference<IRadioButton>>> _registeredGroups = new();
private bool _ignoreCheckedChanges;
public static RadioButtonGroupManager GetOrCreateForRoot(IRenderRoot? root)
public static RadioButtonGroupManager GetOrCreateForRoot(object? root)
{
if (root == null)
return s_default;

2
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;
}

6
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;

296
src/Avalonia.Controls/TopLevel.cs

@ -39,13 +39,9 @@ namespace Avalonia.Controls
/// </remarks>
[TemplatePart("PART_TransparencyFallback", typeof(Border))]
public abstract class TopLevel : ContentControl,
IInputRoot,
ILayoutRoot,
IRenderRoot,
ICloseable,
IStyleHost,
ILogicalRoot,
ITextInputMethodRoot
ILogicalRoot
{
/// <summary>
/// Defines the <see cref="ClientSize"/> property.
@ -59,12 +55,6 @@ namespace Avalonia.Controls
public static readonly DirectProperty<TopLevel, Size?> FrameSizeProperty =
AvaloniaProperty.RegisterDirect<TopLevel, Size?>(nameof(FrameSize), o => o.FrameSize);
/// <summary>
/// Defines the <see cref="IInputRoot.PointerOverElement"/> property.
/// </summary>
public static readonly StyledProperty<IInputElement?> PointerOverElementProperty =
AvaloniaProperty.Register<TopLevel, IInputElement?>(nameof(IInputRoot.PointerOverElement));
/// <summary>
/// Defines the <see cref="TransparencyLevelHint"/> property.
/// </summary>
@ -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<AvaloniaProperty, Action> _platformImplBindings = new();
private double _scaling;
private Size _clientSize;
private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel;
private ILayoutManager? _layoutManager;
private Border? _transparencyFallbackBorder;
private TargetWeakEventSubscriber<TopLevel, ResourcesChangedEventArgs>? _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;
/// <summary>
/// Initializes static members of the <see cref="TopLevel"/> class.
@ -176,19 +164,6 @@ namespace Avalonia.Controls
topLevel?.InvalidateChildInsetsPadding();
});
PointerOverElementProperty.Changed.AddClassHandler<TopLevel>((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
/// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param>
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<IAccessKeyHandler>(dependencyResolver);
_inputManager = TryGetService<IInputManager>(dependencyResolver);
@ -226,13 +206,10 @@ namespace Avalonia.Controls
_globalStyles = TryGetService<IGlobalStyles>(dependencyResolver);
_applicationThemeHost = TryGetService<IThemeVariantHost>(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<ISystemNavigationManagerImpl>() 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;
/// <summary>
/// Gets the platform-specific window implementation.
@ -461,24 +418,23 @@ namespace Avalonia.Controls
/// <summary>
/// Gets the renderer for the window.
/// </summary>
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;
}
/// <summary>
/// Gets a value indicating whether the renderer should draw specific diagnostics.
/// </summary>
public RendererDiagnostics RendererDiagnostics => Renderer.Diagnostics;
internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition;
internal PixelPoint? LastPointerPosition => _source.GetLastPointerPosition(this);
/// <summary>
/// Gets the access key handler for the window.
@ -488,23 +444,6 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the keyboard navigation handler for the window.
/// </summary>
IKeyboardNavigationHandler? IInputRoot.KeyboardNavigationHandler => _keyboardNavigationHandler;
/// <inheritdoc/>
IInputElement? IInputRoot.PointerOverElement
{
get => GetValue(PointerOverElementProperty);
set => SetValue(PointerOverElementProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>
bool IInputRoot.ShowAccessKeys
{
get => GetValue(AccessText.ShowAccessKeyProperty);
set => SetValue(AccessText.ShowAccessKeyProperty, value);
}
/// <summary>
/// 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;
/// <inheritdoc/>
public double RenderScaling => _scaling;
@ -575,23 +512,22 @@ namespace Avalonia.Controls
/// </summary>
public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>();
/// <inheritdoc />
public IFocusManager? FocusManager => _focusManager ??= new FocusManager(this);
/// <inheritdoc />
public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
/// <inheritdoc/>
Point IRenderRoot.PointToClient(PixelPoint p)
{
return PlatformImpl?.PointToClient(p) ?? default;
}
/// <summary>
/// Gets focus manager of the root.
/// </summary>
/// <remarks>
/// Focus manager can be null only if window wasn't initialized yet.
/// </remarks>
public IFocusManager FocusManager => _source.FocusManager;
/// <inheritdoc/>
PixelPoint IRenderRoot.PointToScreen(Point p)
{
return PlatformImpl?.PointToScreen(p) ?? default;
}
/// <summary>
/// Represents a contract for accessing top-level platform-specific settings.
/// </summary>
/// <remarks>
/// PlatformSettings can be null only if window wasn't initialized yet.
/// </remarks>
// TODO: Un-private
private IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
/// <summary>
/// Gets the <see cref="TopLevel" /> for which the given <see cref="Visual"/> is hosted in.
@ -600,7 +536,14 @@ namespace Avalonia.Controls
/// <returns>The TopLevel</returns>
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;
}
/// <summary>
@ -651,7 +594,6 @@ namespace Avalonia.Controls
}
private IDisposable? _insetsPaddings;
private FocusManager? _focusManager;
private void InvalidateChildInsetsPadding()
{
@ -677,11 +619,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Creates the layout manager for this <see cref="TopLevel" />.
/// </summary>
private protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
/// <summary>
/// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>
@ -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);
/// <summary>
/// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>.
/// </summary>
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;
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Handles input from <see cref="ITopLevelImpl.Input"/>.
/// </summary>
/// <param name="e">The event args.</param>
private void HandleInput(RawInputEventArgs e)
{
if (PlatformImpl != null)
{
Dispatcher.UIThread.Send(static state =>
{
using var _ = Diagnostic.BeginLayoutInputPass();
var (topLevel, e) = (ValueTuple<TopLevel, RawInputEventArgs>)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();
}
/// <summary>
/// This should only be used by InProcessDragSource
/// </summary>
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<ITextInputMethodImpl>();
private double ValidateScaling(double scaling)
{
if (MathUtilities.IsNegativeOrNonFinite(scaling) || MathUtilities.IsZero(scaling))
@ -983,49 +833,5 @@ namespace Avalonia.Controls
return scaling;
}
/// <summary>
/// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes.
/// </summary>
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;
}
}
}
}

2
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)
{

2
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.

2
src/Avalonia.Controls/Window.cs

@ -86,7 +86,7 @@ namespace Avalonia.Controls
/// <summary>
/// A top-level window.
/// </summary>
public class Window : WindowBase, IFocusScope, ILayoutRoot
public class Window : WindowBase, IFocusScope
{
private static readonly Lazy<WindowIcon?> s_defaultIcon = new(LoadDefaultIcon);
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();

2
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)

6
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))
{

10
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)));
}

3
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
};
}

4
src/Avalonia.X11/X11Window.cs

@ -267,7 +267,9 @@ namespace Avalonia.X11
? DBusSystemDialog.TryCreateAsync(Handle)
: Task.FromResult<IStorageProvider?>(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)
});

4
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)

2
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()

2
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);

2
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 =>
{

46
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<IInputRoot>();
target.ProcessRawEvent(
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(
target,
KeyboardDevice.Instance,
0,
root.Object,
window.GetInputRoot()!,
RawKeyEventType.KeyDown,
Key.A,
RawInputModifiers.None,
PhysicalKey.A,
"a"));
root.Verify(x => x.RaiseEvent(It.IsAny<KeyEventArgs>()));
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<IInputRoot>();
target.ProcessRawEvent(
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(
target,
KeyboardDevice.Instance,
0,
root.Object,
window.GetInputRoot()!,
"Foo"));
root.Verify(x => x.RaiseEvent(It.IsAny<TextInputEventArgs>()));
Assert.Equal(1, raised);
}
}
[Fact]

6
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(

10
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<IPointer>().Object, (Visual)root,
return new PointerEventArgs(InputElement.PointerMovedEvent, source, new Mock<IPointer>().Object, root.RootElement,
position ?? default, default, PointerPointProperties.None, KeyModifiers.None);
}

18
tests/Avalonia.Base.UnitTests/VisualTests.cs

@ -76,15 +76,16 @@ namespace Avalonia.Base.UnitTests
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;
};

11
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;
}
}
}

2
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()

2
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,

2
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)

2
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

11
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<ITopLevelImpl> CreateMockTopLevelImpl()

6
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -57,7 +57,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var parentMock = new Mock<Control>();
parentMock.As<IContentPresenterHost>();
parentMock.As<IRenderRoot>();
parentMock.As<IPresentationSource>();
parentMock.As<ILogicalRoot>();
(target as ISetLogicalParent).SetParent(parentMock.Object);
@ -102,7 +102,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
};
var parentMock = new Mock<Control>();
parentMock.As<IRenderRoot>();
parentMock.As<IPresentationSource>();
parentMock.As<ILogicalRoot>();
parentMock.As<ILogical>().SetupGet(l => l.IsAttachedToLogicalTree).Returns(true);
@ -148,7 +148,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var parentMock = new Mock<Control>();
parentMock.As<IContentPresenterHost>();
parentMock.As<IRenderRoot>();
parentMock.As<IPresentationSource>();
parentMock.As<ILogicalRoot>();
(target as ISetLogicalParent).SetParent(parentMock.Object);

21
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -657,13 +657,22 @@ namespace Avalonia.Controls.UnitTests.Primitives
button.Focus();
var inputRoot = Assert.IsAssignableFrom<IInputRoot>(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<IInputRoot>(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),

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save