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 --> <!-- 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"> <Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression> <Suppression>
@ -109,6 +109,42 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target> <Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
@ -151,6 +187,24 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Styling.IStyleable</Target> <Target>T:Avalonia.Styling.IStyleable</Target>
@ -391,6 +445,42 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target> <Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
@ -433,6 +523,24 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Styling.IStyleable</Target> <Target>T:Avalonia.Styling.IStyleable</Target>
@ -685,6 +793,42 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target> <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> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <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> <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> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target> <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> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.ContextMenu.PlacementModeProperty</Target> <Target>F:Avalonia.Controls.ContextMenu.PlacementModeProperty</Target>
@ -1063,6 +1243,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AppBuilder.get_LifetimeOverride</Target> <Target>M:Avalonia.AppBuilder.get_LifetimeOverride</Target>
@ -1333,6 +1519,30 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TreeView.get_ItemContainerGenerator</Target> <Target>M:Avalonia.Controls.TreeView.get_ItemContainerGenerator</Target>
@ -1603,6 +1813,42 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target> <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> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <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> <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> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target> <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> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.ContextMenu.PlacementModeProperty</Target> <Target>F:Avalonia.Controls.ContextMenu.PlacementModeProperty</Target>
@ -1981,6 +2263,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AppBuilder.get_LifetimeOverride</Target> <Target>M:Avalonia.AppBuilder.get_LifetimeOverride</Target>
@ -2251,6 +2539,30 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.TreeView.get_ItemContainerGenerator</Target> <Target>M:Avalonia.Controls.TreeView.get_ItemContainerGenerator</Target>
@ -2839,6 +3151,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Media.ImmediateDrawingContext</Target> <Target>T:Avalonia.Media.ImmediateDrawingContext</Target>
@ -2881,6 +3199,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase</Target> <Target>T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase</Target>
@ -2893,6 +3217,36 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Platform.IPopupImpl</Target> <Target>T:Avalonia.Platform.IPopupImpl</Target>
@ -2917,6 +3271,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Metal.IMetalDevice</Target> <Target>T:Avalonia.Metal.IMetalDevice</Target>
@ -2959,6 +3319,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Media.ImmediateDrawingContext</Target> <Target>T:Avalonia.Media.ImmediateDrawingContext</Target>
@ -3001,6 +3367,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase</Target> <Target>T:Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase</Target>
@ -3013,6 +3385,36 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Platform.IPopupImpl</Target> <Target>T:Avalonia.Platform.IPopupImpl</Target>
@ -3037,6 +3439,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0008</DiagnosticId> <DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Metal.IMetalDevice</Target> <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) 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; window.SystemDecorations = systemDecorations;
} }
@ -78,7 +78,7 @@ namespace ControlCatalog
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
if (VisualRoot is Window window) if (TopLevel.GetTopLevel(this) is Window window)
Decorations.SelectedIndex = (int)window.SystemDecorations; Decorations.SelectedIndex = (int)window.SystemDecorations;
var insets = TopLevel.GetTopLevel(this)!.InsetsManager; var insets = TopLevel.GetTopLevel(this)!.InsetsManager;

2
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@ -49,7 +49,7 @@ namespace ControlCatalog.ViewModels
public async Task Open() public async Task Open()
{ {
var window = View?.GetVisualRoot() as Window; var window = TopLevel.GetTopLevel(View) as Window;
if (window == null) if (window == null)
return; return;

2
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -69,7 +69,7 @@ namespace ControlCatalog.ViewModels
public async Task Open() public async Task Open()
{ {
var window = View?.GetVisualRoot() as Window; var window = TopLevel.GetTopLevel(View) as Window;
if (window == null) if (window == null)
return; return;
var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true }); 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() void UpdateFrame()
{ {
_updateQueued = false; _updateQueued = false;
var root = this.GetVisualRoot(); var source = this.GetPresentationSource();
if (root == null) if (source == null)
return; return;
_visual!.Size = new (Bounds.Width, Bounds.Height); _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); RenderFrame(size);
if (SupportsDisco && Disco > 0) if (SupportsDisco && Disco > 0)
QueueNextFrame(); QueueNextFrame();

3
src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs

@ -157,8 +157,7 @@ namespace Avalonia.Android.Platform.Input
} }
case ImeAction.Next: case ImeAction.Next:
{ {
FocusManager.GetFocusManager(_toplevel.InputRoot)? ((FocusManager?)_toplevel.InputRoot?.FocusManager)?.TryMoveFocus(NavigationDirection.Next);
.TryMoveFocus(NavigationDirection.Next);
break; break;
} }
} }

26
src/Avalonia.Base/Input/AccessKeyHandler.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -30,6 +31,12 @@ namespace Avalonia.Input
RoutingStrategies.Bubble, RoutingStrategies.Bubble,
typeof(AccessKeyHandler)); typeof(AccessKeyHandler));
/// <summary>
/// Defines the ShowAccessKey attached property.
/// </summary>
public static readonly AttachedProperty<bool> ShowAccessKeyProperty =
AvaloniaProperty.RegisterAttached<AccessKeyHandler, Visual, bool>("ShowAccessKey", inherits: true);
/// <summary> /// <summary>
/// The registered access keys. /// The registered access keys.
/// </summary> /// </summary>
@ -40,7 +47,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// The window to which the handler belongs. /// The window to which the handler belongs.
/// </summary> /// </summary>
private IInputRoot? _owner; private InputElement? _owner;
/// <summary> /// <summary>
/// Whether access keys are currently being shown; /// Whether access keys are currently being shown;
@ -96,7 +103,7 @@ namespace Avalonia.Input
/// <remarks> /// <remarks>
/// This method can only be called once, typically by the owner itself on creation. /// This method can only be called once, typically by the owner itself on creation.
/// </remarks> /// </remarks>
public void SetOwner(IInputRoot owner) public void SetOwner(InputElement owner)
{ {
if (_owner != null) if (_owner != null)
{ {
@ -113,7 +120,7 @@ namespace Avalonia.Input
OnSetOwner(owner); 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> /// <summary>
/// Called when a key is pressed in the owner window. /// Called when a key is pressed in the owner window.
/// </summary> /// </summary>
@ -188,7 +198,7 @@ namespace Avalonia.Input
// When Alt is pressed without a main menu, or with a closed main menu, show // When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File"). // access key markers in the window (i.e. "_File").
_owner!.ShowAccessKeys = _showingAccessKeys = isFocusWithinOwner; SetShowAccessKeys(_owner!, _showingAccessKeys = isFocusWithinOwner);
} }
else else
{ {
@ -265,7 +275,7 @@ namespace Avalonia.Input
{ {
if (_showingAccessKeys) if (_showingAccessKeys)
{ {
_owner!.ShowAccessKeys = false; SetShowAccessKeys(_owner!, false);
} }
} }
@ -275,12 +285,12 @@ namespace Avalonia.Input
private void CloseMenu() private void CloseMenu()
{ {
MainMenu!.Close(); MainMenu!.Close();
_owner!.ShowAccessKeys = _showingAccessKeys = false; SetShowAccessKeys(_owner!, _showingAccessKeys = false);
} }
private void MainMenuClosed(object? sender, EventArgs e) private void MainMenuClosed(object? sender, EventArgs e)
{ {
_owner!.ShowAccessKeys = false; SetShowAccessKeys(_owner!, false);
} }
/// <summary> /// <summary>
@ -444,7 +454,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="owner">The owner to check.</param> /// <param name="owner">The owner to check.</param>
/// <returns>If focused element is decendant of owner <c>true</c>, otherwise <c>false</c>. </returns> /// <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; var focusedElement = KeyboardDevice.Instance?.FocusedElement;
if (focusedElement is not InputElement inputElement) 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) 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(); var target = hit?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target)) if (target != null && DragDrop.GetAllowDrop(target))
return target; return target;
@ -35,7 +35,7 @@ namespace Avalonia.Input
if (target == null) if (target == null)
return DragDropEffects.None; return DragDropEffects.None;
var p = ((Visual)inputRoot).TranslatePoint(point, target); var p = (inputRoot.RootElement).TranslatePoint(point, target);
if (!p.HasValue) if (!p.HasValue)
return DragDropEffects.None; return DragDropEffects.None;

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

@ -52,6 +52,11 @@ namespace Avalonia.Input
_contentRoot = contentRoot; _contentRoot = contentRoot;
} }
internal void SetContentRoot(IInputElement? contentRoot)
{
_contentRoot = contentRoot;
}
private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement; private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
private XYFocus _xyFocus = new(); private XYFocus _xyFocus = new();
@ -163,9 +168,10 @@ namespace Avalonia.Input
/// </summary> /// </summary>
internal static FocusManager? GetFocusManager(IInputElement? element) internal static FocusManager? GetFocusManager(IInputElement? element)
{ {
// Element might not be a visual, and not attached to the root. // Element might not be a visual, and not attached to the root.
// But IFocusManager is always expected to be a FocusManager. // 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. // In our unit tests some elements might not have a root. Remove when we migrate to headless tests.
?? (FocusManager?)AvaloniaLocator.Current.GetService<IFocusManager>(); ?? (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_lastPressPoint = e.GetPosition((Visual)source);
s_holdCancellationToken = new CancellationTokenSource(); s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token; var token = s_holdCancellationToken.Token;
var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings; var settings = visual.GetPlatformSettings();
if (settings != null) if (settings != null)
{ {
@ -298,7 +298,7 @@ namespace Avalonia.Input
source is Interactive i) source is Interactive i)
{ {
var point = e.GetCurrentPoint((Visual)target); 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 tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size()) var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height)); .Inflate(new Thickness(tapSize.Width, tapSize.Height));
@ -340,7 +340,6 @@ namespace Avalonia.Input
if (e.Pointer == s_gestureState?.Pointer && source is Interactive i) if (e.Pointer == s_gestureState?.Pointer && source is Interactive i)
{ {
var point = e.GetCurrentPoint((Visual)target); var point = e.GetCurrentPoint((Visual)target);
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
var holdSize = new Size(4, 4); var holdSize = new Size(4, 4);
var holdRect = new Rect(s_lastPressPoint, new Size()) var holdRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(holdSize.Width, holdSize.Height)); .Inflate(new Thickness(holdSize.Width, holdSize.Height));

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

@ -19,7 +19,7 @@ namespace Avalonia.Input
/// <remarks> /// <remarks>
/// This method can only be called once, typically by the owner itself on creation. /// This method can only be called once, typically by the owner itself on creation.
/// </remarks> /// </remarks>
void SetOwner(IInputRoot owner); void SetOwner(InputElement owner);
/// <summary> /// <summary>
/// Registers an input element to be associated with an access key. /// 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.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
@ -6,38 +7,24 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Defines the interface for top-level input elements. /// Defines the interface for top-level input elements.
/// </summary> /// </summary>
[NotClientImplementable] [PrivateApi]
public interface IInputRoot : IInputElement public interface IInputRoot
{ {
/// <summary>
/// Gets or sets the keyboard navigation handler.
/// </summary>
IKeyboardNavigationHandler? KeyboardNavigationHandler { get; }
/// <summary> /// <summary>
/// Gets focus manager of the root. /// Gets focus manager of the root.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Focus manager can be null only if window wasn't initialized yet. /// Focus manager can be null only if window wasn't initialized yet.
/// </remarks> /// </remarks>
IFocusManager? FocusManager { get; } public 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; }
/// <summary> /// <summary>
/// Gets or sets the input element that the pointer is currently over. /// Gets or sets the input element that the pointer is currently over.
/// </summary> /// </summary>
IInputElement? PointerOverElement { get; set; } internal IInputElement? PointerOverElement { get; set; }
/// <summary> internal ITextInputMethodImpl? InputMethod { get; }
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> internal InputElement RootElement { get; }
bool ShowAccessKeys { get; set; }
} }
} }

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. /// Defines the interface for classes that handle keyboard navigation for a window.
/// </summary> /// </summary>
[Unstable] [Unstable]
public interface IKeyboardNavigationHandler internal interface IKeyboardNavigationHandler
{ {
/// <summary> /// <summary>
/// Sets the owner of the keyboard navigation handler. /// 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. /// This method can only be called once, typically by the owner itself on creation.
/// </remarks> /// </remarks>
[PrivateApi] [PrivateApi]
void SetOwner(IInputRoot owner); void SetOwner(InputElement owner);
/// <summary> /// <summary>
/// Moves the focus in the specified direction. /// Moves the focus in the specified direction.

4
src/Avalonia.Base/Input/InputElement.cs

@ -583,7 +583,9 @@ namespace Avalonia.Input
if (IsFocused) 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; IsKeyboardFocusWithin = false;

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

@ -191,15 +191,15 @@ namespace Avalonia.Input
// Clear keyboard focus from currently focused element // Clear keyboard focus from currently focused element
if (FocusedElement != null && if (FocusedElement != null &&
(!((Visual)FocusedElement).IsAttachedToVisualTree || (!((Visual)FocusedElement).IsAttachedToVisualTree ||
_focusedRoot != ((Visual?)element)?.VisualRoot as IInputRoot) && _focusedRoot != ((Visual?)element)?.GetInputRoot()) &&
_focusedRoot != null) _focusedRoot != null)
{ {
ClearChildrenFocusWithin(_focusedRoot, true); ClearChildrenFocusWithin(_focusedRoot.RootElement, true);
} }
SetIsFocusWithin(FocusedElement, element); SetIsFocusWithin(FocusedElement, element);
_focusedElement = element; _focusedElement = element;
_focusedRoot = ((Visual?)_focusedElement)?.VisualRoot as IInputRoot; _focusedRoot = (_focusedElement as Visual)?.GetInputRoot();
interactive?.RaiseEvent(new RoutedEventArgs(InputElement.LostFocusEvent)); interactive?.RaiseEvent(new RoutedEventArgs(InputElement.LostFocusEvent));
@ -225,7 +225,7 @@ namespace Avalonia.Input
if(e.Handled) if(e.Handled)
return; return;
var element = FocusedElement ?? e.Root; var element = FocusedElement ?? e.Root.RootElement;
if (e is RawKeyEventArgs keyInput) if (e is RawKeyEventArgs keyInput)
{ {

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

@ -10,13 +10,12 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Handles keyboard navigation for a window. /// Handles keyboard navigation for a window.
/// </summary> /// </summary>
[Unstable] internal sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
public sealed class KeyboardNavigationHandler : IKeyboardNavigationHandler
{ {
/// <summary> /// <summary>
/// The window to which the handler belongs. /// The window to which the handler belongs.
/// </summary> /// </summary>
private IInputRoot? _owner; private InputElement? _owner;
/// <summary> /// <summary>
/// Sets the owner of the keyboard navigation handler. /// 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. /// This method can only be called once, typically by the owner itself on creation.
/// </remarks> /// </remarks>
[PrivateApi] [PrivateApi]
public void SetOwner(IInputRoot owner) public void SetOwner(InputElement owner)
{ {
if (_owner != null) if (_owner != null)
{ {
@ -56,7 +55,7 @@ namespace Avalonia.Input
private static IInputElement? GetNextPrivate( private static IInputElement? GetNextPrivate(
IInputElement? element, IInputElement? element,
IInputRoot? owner, InputElement? owner,
NavigationDirection direction, NavigationDirection direction,
KeyDeviceType? keyDeviceType) KeyDeviceType? keyDeviceType)
{ {

20
src/Avalonia.Base/Input/MouseDevice.cs

@ -135,20 +135,20 @@ namespace Avalonia.Input
return new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()); 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, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest) KeyModifiers inputModifiers, IInputElement? hitTest)
{ {
device = device ?? throw new ArgumentNullException(nameof(device)); device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root)); 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) if (source != null)
{ {
_pointer.Capture(source, CaptureSource.Implicit); _pointer.Capture(source, CaptureSource.Implicit);
var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings; var settings = (source as Interactive)?.GetPlatformSettings();
if (settings is not null) if (settings is not null)
{ {
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds; var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds;
@ -166,7 +166,7 @@ namespace Avalonia.Input
} }
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); _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); source.RaiseEvent(e);
return e.Handled; return e.Handled;
} }
@ -185,7 +185,7 @@ namespace Avalonia.Input
if (source is object) 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); p, timestamp, properties, inputModifiers, intermediatePoints);
if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
@ -209,7 +209,7 @@ namespace Avalonia.Input
if (source is not null) 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); _lastMouseDownButton);
try try
@ -244,7 +244,7 @@ namespace Avalonia.Input
if (source is not null) 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); source?.RaiseEvent(e);
return e.Handled; return e.Handled;
@ -264,7 +264,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureMagnifyEvent, source, 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); source?.RaiseEvent(e);
return e.Handled; return e.Handled;
@ -284,7 +284,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureRotateEvent, source, 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); source?.RaiseEvent(e);
return e.Handled; return e.Handled;
@ -304,7 +304,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureSwipeEvent, source, 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); source?.RaiseEvent(e);
return e.Handled; 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) private static bool IsOccluded(InputElement element, Rect elementBounds)
{ {
// if (element is CHyperlink hyperlink) // TODO: The check for bounds is no longer correct
// {
// element = hyperlink.GetContainingFrameworkElement();
// }
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 // Check if the element is within the visible area of the window
var visibleBounds = new Rect(0, 0, root.Bounds.Width, root.Bounds.Height); 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; 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 isRightToLeft = element.FlowDirection == FlowDirection.RightToLeft;
var mode = GetStrategy(element, direction, xyFocusOptions.NavigationStrategyOverride); 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, private bool PenDown(Pointer pointer, ulong timestamp,
IInputElement root, Point p, PointerPointProperties properties, IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest) KeyModifiers inputModifiers, IInputElement? hitTest)
{ {
var source = pointer.Captured ?? hitTest; var source = pointer.Captured ?? hitTest;
@ -105,7 +105,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
pointer.Capture(source); pointer.Capture(source);
var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings; var settings = (source as Interactive)?.GetPlatformSettings();
if (settings is not null) if (settings is not null)
{ {
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds; var doubleClickTime = settings.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds;
@ -123,7 +123,7 @@ namespace Avalonia.Input
} }
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); _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); source.RaiseEvent(e);
return e.Handled; return e.Handled;
} }
@ -140,7 +140,7 @@ namespace Avalonia.Input
if (source is not null) 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); p, timestamp, properties, inputModifiers, intermediatePoints);
if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
@ -154,14 +154,14 @@ namespace Avalonia.Input
} }
private bool PenUp(Pointer pointer, ulong timestamp, private bool PenUp(Pointer pointer, ulong timestamp,
IInputElement root, Point p, PointerPointProperties properties, IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest) KeyModifiers inputModifiers, IInputElement? hitTest)
{ {
var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest; var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
if (source is not null) 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); _lastMouseDownButton);
try 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>(); return parent as IInputElement ?? parent.FindAncestorOfType<IInputElement>();
} }
private void OnCaptureDetached(object? sender, VisualTreeAttachmentEventArgs e) 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. // occurred.
// //
// Solve this by updating the last known pointer position when a drag event occurs. // 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 else if (value is RawPointerEventArgs args
@ -64,7 +64,7 @@ namespace Avalonia.Input
args.InputModifiers.ToKeyModifiers()); 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); _lastKnownPosition = visual.PointToScreen(args.Position);
} }
@ -99,12 +99,12 @@ namespace Avalonia.Input
if (dirtyRect.Contains(clientPoint)) if (dirtyRect.Contains(clientPoint))
{ {
var element = GetEffectivePointerOverElement( var element = GetEffectivePointerOverElement(
_inputRoot.InputHitTest(clientPoint), _inputRoot.RootElement.InputHitTest(clientPoint),
pointer.Captured); pointer.Captured);
SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); 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); ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
} }
@ -140,16 +140,16 @@ namespace Avalonia.Input
// so GetPosition won't return invalid values. // so GetPosition won't return invalid values.
#pragma warning disable CS0618 #pragma warning disable CS0618
var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer, 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); timestamp, properties, inputModifiers);
#pragma warning restore CS0618 #pragma warning restore CS0618
if (element is Visual v && !v.IsAttachedToVisualTree) if (element is Visual v && !v.IsAttachedToVisualTree)
{ {
// element has been removed from visual tree so do top down cleanup // 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) while (element != null)
@ -191,7 +191,7 @@ namespace Avalonia.Input
ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers) ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers)
{ {
var pointerOverElement = root.PointerOverElement; var pointerOverElement = root.PointerOverElement;
var screenPosition = ((Visual)root).PointToScreen(position); var screenPosition = (root.RootElement).PointToScreen(position);
_lastKnownPosition = screenPosition; _lastKnownPosition = screenPosition;
if (element != pointerOverElement) if (element != pointerOverElement)
@ -229,7 +229,7 @@ namespace Avalonia.Input
el = root.PointerOverElement; el = root.PointerOverElement;
#pragma warning disable CS0618 #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); timestamp, properties, inputModifiers);
#pragma warning restore CS0618 #pragma warning restore CS0618
if (el is Visual v && branch != null && !v.IsAttachedToVisualTree) if (el is Visual v && branch != null && !v.IsAttachedToVisualTree)
@ -265,7 +265,7 @@ namespace Avalonia.Input
private static Point PointToClient(IInputRoot root, PixelPoint p) 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 SetOptions(TextInputOptions options);
void Reset(); 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, InputMethod.AddTextInputMethodClientRequeryRequestedHandler(_visualRoot,
TextInputMethodClientRequeryRequested); TextInputMethodClientRequeryRequested);
var inputMethod = ((element as Visual)?.VisualRoot as ITextInputMethodRoot)?.InputMethod; var inputMethod = ((element as Visual)?.GetInputRoot())?.InputMethod;
if (_im != inputMethod) if (_im != inputMethod)
{ {

10
src/Avalonia.Base/Input/TouchDevice.cs

@ -51,7 +51,7 @@ namespace Avalonia.Input
pointer.Capture(hit); 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 gestureTarget = pointer.CapturedGestureRecognizer?.Target;
var updateKind = args.Type.ToUpdateKind(); var updateKind = args.Type.ToUpdateKind();
var keyModifier = args.InputModifiers.ToKeyModifiers(); var keyModifier = args.InputModifiers.ToKeyModifiers();
@ -66,7 +66,7 @@ namespace Avalonia.Input
} }
else else
{ {
var settings = ((IInputRoot?)(target as Interactive)?.GetVisualRoot())?.PlatformSettings; var settings = (target as Interactive)?.GetPlatformSettings();
if (settings is not null) if (settings is not null)
{ {
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds; var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
@ -86,7 +86,7 @@ namespace Avalonia.Input
} }
target.RaiseEvent(new PointerPressedEventArgs(target, pointer, 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), new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind, args.Point),
keyModifier, _clickCount)); keyModifier, _clickCount));
} }
@ -98,7 +98,7 @@ namespace Avalonia.Input
{ {
target = gestureTarget ?? target; target = gestureTarget ?? target;
var e = new PointerReleasedEventArgs(target, pointer, 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), new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind, args.Point),
keyModifier, MouseButton.Left); keyModifier, MouseButton.Left);
if (gestureTarget != null) if (gestureTarget != null)
@ -127,7 +127,7 @@ namespace Avalonia.Input
if (args.Type == RawPointerEventType.TouchUpdate) if (args.Type == RawPointerEventType.TouchUpdate)
{ {
target = gestureTarget ?? target; 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, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind, args.Point), new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind, args.Point),
keyModifier, args.IntermediatePoints); 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> /// <summary>
/// Manages measuring and arranging of controls. /// Manages measuring and arranging of controls.
/// </summary> /// </summary>
[PrivateApi]
public interface ILayoutManager : IDisposable public interface ILayoutManager : IDisposable
{ {
/// <summary> /// <summary>

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

@ -5,22 +5,18 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Defines the root of a layoutable tree. /// Defines the root of a layoutable tree.
/// </summary> /// </summary>
[NotClientImplementable] internal interface ILayoutRoot
public interface ILayoutRoot
{ {
/// <summary>
/// The size available to lay out the controls.
/// </summary>
Size ClientSize { get; }
/// <summary> /// <summary>
/// The scaling factor to use in layout. /// The scaling factor to use in layout.
/// </summary> /// </summary>
double LayoutScaling { get; } public double LayoutScaling { get; }
/// <summary> /// <summary>
/// Associated instance of layout manager /// Associated instance of layout manager
/// </summary> /// </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> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
/// <exception cref="Exception">Thrown when control has no root or returned layout scaling is invalid.</exception> /// <exception cref="Exception">Thrown when control has no root or returned layout scaling is invalid.</exception>
public static double GetLayoutScale(Layoutable control) public static double GetLayoutScale(Layoutable control) => control.GetLayoutRoot()?.LayoutScaling ?? 1.0;
=> control.VisualRoot is ILayoutRoot layoutRoot ? layoutRoot.LayoutScaling : 1.0;
/// <summary> /// <summary>
/// Rounds a size to integer values for layout purposes, compensating for high DPI screen /// 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.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.VisualTree;
#nullable enable #nullable enable
@ -17,11 +18,10 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Manages measuring and arranging of controls. /// Manages measuring and arranging of controls.
/// </summary> /// </summary>
[PrivateApi] internal class LayoutManager : ILayoutManager, IDisposable
public class LayoutManager : ILayoutManager, IDisposable
{ {
private const int MaxPasses = 10; 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> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid); private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid);
private readonly List<Layoutable> _toArrangeAfterMeasure = new(); private readonly List<Layoutable> _toArrangeAfterMeasure = new();
@ -34,7 +34,7 @@ namespace Avalonia.Layout
public LayoutManager(ILayoutRoot owner) public LayoutManager(ILayoutRoot owner)
{ {
_owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner)); _owner = owner;
_invokeOnRender = ExecuteQueuedLayoutPass; _invokeOnRender = ExecuteQueuedLayoutPass;
} }
@ -63,7 +63,7 @@ namespace Avalonia.Layout
#endif #endif
} }
if (control.VisualRoot != _owner) if (control.GetLayoutRoot() != _owner)
{ {
throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager."); throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
} }
@ -93,7 +93,7 @@ namespace Avalonia.Layout
#endif #endif
} }
if (control.VisualRoot != _owner) if (control.GetLayoutRoot() != _owner)
{ {
throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager."); throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
} }
@ -188,9 +188,12 @@ namespace Avalonia.Layout
try try
{ {
if (_owner?.RootVisual == null)
return;
var root = _owner.RootVisual;
_running = true; _running = true;
Measure(_owner); Measure(root);
Arrange(_owner); Arrange(root);
} }
finally finally
{ {
@ -300,7 +303,7 @@ namespace Avalonia.Layout
// control to be removed. // control to be removed.
if (!control.IsMeasureValid) if (!control.IsMeasureValid)
{ {
if (control is ILayoutRoot root) if (control.GetLayoutRoot()?.RootVisual == control)
{ {
control.Measure(Size.Infinity); control.Measure(Size.Infinity);
} }
@ -329,9 +332,7 @@ namespace Avalonia.Layout
if (!control.IsArrangeValid) if (!control.IsArrangeValid)
{ {
if (control is IEmbeddedLayoutRoot embeddedRoot) if (control.GetLayoutRoot()?.RootVisual == control)
control.Arrange(new Rect(embeddedRoot.AllocatedSize));
else if (control is ILayoutRoot root)
control.Arrange(new Rect(control.DesiredSize)); control.Arrange(new Rect(control.DesiredSize));
else if (control.PreviousArrange != null) else if (control.PreviousArrange != null)
{ {

21
src/Avalonia.Base/Layout/Layoutable.cs

@ -168,7 +168,7 @@ namespace Avalonia.Layout
{ {
add add
{ {
if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree) if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{ {
r.LayoutManager.RegisterEffectiveViewportListener(this); r.LayoutManager.RegisterEffectiveViewportListener(this);
} }
@ -180,7 +180,7 @@ namespace Avalonia.Layout
{ {
_effectiveViewportChanged -= value; _effectiveViewportChanged -= value;
if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r) if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r)
{ {
r.LayoutManager.UnregisterEffectiveViewportListener(this); r.LayoutManager.UnregisterEffectiveViewportListener(this);
} }
@ -194,7 +194,7 @@ namespace Avalonia.Layout
{ {
add add
{ {
if (_layoutUpdated is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree) if (_layoutUpdated is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{ {
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated; r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
} }
@ -206,7 +206,7 @@ namespace Avalonia.Layout
{ {
_layoutUpdated -= value; _layoutUpdated -= value;
if (_layoutUpdated is null && VisualRoot is ILayoutRoot r) if (_layoutUpdated is null && this.GetLayoutRoot() is {} r)
{ {
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated; 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 /// You should not usually need to call this method explictly, the layout manager will
/// schedule layout passes itself. /// schedule layout passes itself.
/// </remarks> /// </remarks>
public void UpdateLayout() => (this.GetVisualRoot() as ILayoutRoot)?.LayoutManager?.ExecuteLayoutPass();
public void UpdateLayout() => this.GetLayoutManager()?.ExecuteLayoutPass();
/// <summary> /// <summary>
/// Gets or sets the width of the element. /// Gets or sets the width of the element.
@ -448,7 +449,7 @@ namespace Avalonia.Layout
if (IsAttachedToVisualTree) if (IsAttachedToVisualTree)
{ {
(VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this); this.GetLayoutManager()?.InvalidateMeasure(this);
InvalidateVisual(); InvalidateVisual();
} }
OnMeasureInvalidated(); OnMeasureInvalidated();
@ -465,7 +466,7 @@ namespace Avalonia.Layout
Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Invalidated arrange"); Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Invalidated arrange");
IsArrangeValid = false; IsArrangeValid = false;
(VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this); this.GetLayoutManager()?.InvalidateArrange(this);
InvalidateVisual(); InvalidateVisual();
} }
} }
@ -793,7 +794,7 @@ namespace Avalonia.Layout
_isAttachingToVisualTree = false; _isAttachingToVisualTree = false;
} }
if (e.Root is ILayoutRoot r) if (this.GetLayoutRoot() is {} r)
{ {
if (_layoutUpdated is object) if (_layoutUpdated is object)
{ {
@ -809,7 +810,7 @@ namespace Avalonia.Layout
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
if (e.Root is ILayoutRoot r) if (this.GetLayoutRoot() is {} r)
{ {
if (_layoutUpdated is object) if (_layoutUpdated is object)
{ {
@ -852,7 +853,7 @@ namespace Avalonia.Layout
// they will need to be registered with the layout manager now that they // they will need to be registered with the layout manager now that they
// are again effectively visible. If IsEffectivelyVisible becomes an observable // are again effectively visible. If IsEffectivelyVisible becomes an observable
// property then we can piggy-pack on that; for the moment we do this manually. // 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; var count = VisualChildren.Count;

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

@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition;
/// </summary> /// </summary>
internal class CompositingRenderer : IRendererWithCompositor, IHitTester internal class CompositingRenderer : IRendererWithCompositor, IHitTester
{ {
private readonly IRenderRoot _root; private readonly IPresentationSource _root;
private readonly Compositor _compositor; private readonly Compositor _compositor;
private readonly RenderDataDrawingContext _recorder; private readonly RenderDataDrawingContext _recorder;
private readonly HashSet<Visual> _dirty = new(); private readonly HashSet<Visual> _dirty = new();
@ -48,13 +48,12 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
/// <param name="surfaces"> /// <param name="surfaces">
/// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems. /// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
/// </param> /// </param>
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces) public CompositingRenderer(IPresentationSource root, Compositor compositor, Func<IEnumerable<object>> surfaces)
{ {
_root = root; _root = root;
_compositor = compositor; _compositor = compositor;
_recorder = new(compositor); _recorder = new(compositor);
CompositionTarget = compositor.CreateCompositionTarget(surfaces); CompositionTarget = compositor.CreateCompositionTarget(surfaces);
CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
_update = Update; _update = Update;
Diagnostics = new RendererDiagnostics(); Diagnostics = new RendererDiagnostics();
Diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged; Diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
@ -188,13 +187,13 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
commit.Rendered.ContinueWith(_ => Dispatcher.UIThread.Post(() => commit.Rendered.ContinueWith(_ => Dispatcher.UIThread.Post(() =>
{ {
_queuedSceneInvalidation = false; _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); }, DispatcherPriority.Input), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
} }
} }
public void TriggerSceneInvalidatedForUnitTests(Rect rect) => public void TriggerSceneInvalidatedForUnitTests(Rect rect) =>
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, rect)); SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(rect));
private void Update() 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"); throw new InvalidOperationException("Composition visuals belong to different compositor instances");
visual.ChildCompositionVisual = compositionVisual; visual.ChildCompositionVisual = compositionVisual;
visual.GetVisualRoot()?.Renderer.RecalculateChildren(visual); visual.GetPresentationSource()?.Renderer.RecalculateChildren(visual);
} }
/// <summary> /// <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. /// Defines the interface for a renderer.
/// </summary> /// </summary>
[PrivateApi] [PrivateApi]
public interface IRenderer : IDisposable internal interface IRenderer : IDisposable
{ {
/// <summary> /// <summary>
/// Gets a value indicating whether the renderer should draw specific diagnostics. /// Gets a value indicating whether the renderer should draw specific diagnostics.
@ -75,7 +75,7 @@ namespace Avalonia.Rendering
} }
[PrivateApi] [PrivateApi]
public interface IHitTester internal interface IHitTester
{ {
/// <summary> /// <summary>
/// Hit tests a location to find the visuals at the specified point. /// 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> /// <summary>
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class. /// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="root">The render root that has been updated.</param>
/// <param name="dirtyRect">The updated area.</param> /// <param name="dirtyRect">The updated area.</param>
public SceneInvalidatedEventArgs( public SceneInvalidatedEventArgs(Rect dirtyRect)
IRenderRoot root,
Rect dirtyRect)
{ {
RenderRoot = root;
DirtyRect = dirtyRect; DirtyRect = dirtyRect;
} }
@ -27,9 +23,5 @@ namespace Avalonia.Rendering
/// </summary> /// </summary>
public Rect DirtyRect { get; } 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.Collections;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
@ -124,7 +126,7 @@ namespace Avalonia
(s, h) => s.Invalidated -= h); (s, h) => s.Invalidated -= h);
private Rect _bounds; private Rect _bounds;
private IRenderRoot? _visualRoot; internal IPresentationSource? PresentationSource { get; private set; }
private Visual? _visualParent; private Visual? _visualParent;
private bool _hasMirrorTransform; private bool _hasMirrorTransform;
private TargetWeakEventSubscriber<Visual, EventArgs>? _affectsRenderWeakSubscriber; private TargetWeakEventSubscriber<Visual, EventArgs>? _affectsRenderWeakSubscriber;
@ -154,8 +156,6 @@ namespace Avalonia
/// </summary> /// </summary>
public Visual() public Visual()
{ {
_visualRoot = this as IRenderRoot;
// Disable transitions until we're added to the visual tree. // Disable transitions until we're added to the visual tree.
DisableTransitions(); DisableTransitions();
@ -339,7 +339,9 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets the root of the visual tree, if the control is attached to a visual tree. /// Gets the root of the visual tree, if the control is attached to a visual tree.
/// </summary> /// </summary>
protected internal IRenderRoot? VisualRoot => _visualRoot; protected internal Visual? VisualRoot => PresentationSource?.RootVisual;
internal IInputRoot? GetInputRoot() => PresentationSource?.InputRoot;
internal RenderOptions RenderOptions internal RenderOptions RenderOptions
{ {
@ -366,7 +368,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets a value indicating whether this control is attached to a visual root. /// Gets a value indicating whether this control is attached to a visual root.
/// </summary> /// </summary>
internal bool IsAttachedToVisualTree => VisualRoot != null; internal bool IsAttachedToVisualTree => this.PresentationSource != null;
/// <summary> /// <summary>
/// Gets the control's parent visual. /// Gets the control's parent visual.
@ -409,7 +411,7 @@ namespace Avalonia
/// </summary> /// </summary>
public void InvalidateVisual() public void InvalidateVisual()
{ {
VisualRoot?.Renderer.AddDirty(this); PresentationSource?.Renderer.AddDirty(this);
} }
/// <summary> /// <summary>
@ -514,7 +516,7 @@ namespace Avalonia
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
base.LogicalChildrenCollectionChanged(sender, e); base.LogicalChildrenCollectionChanged(sender, e);
VisualRoot?.Renderer.RecalculateChildren(this); PresentationSource?.Renderer.RecalculateChildren(this);
} }
/// <summary> /// <summary>
@ -526,12 +528,8 @@ namespace Avalonia
{ {
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Attached to visual tree"); Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Attached to visual tree");
_visualRoot = e.Root; PresentationSource = e.PresentationSource;
RootedVisualChildrenCount++; 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) if (RenderTransform is IMutableTransform mutableTransform)
{ {
@ -539,27 +537,30 @@ namespace Avalonia
} }
EnableTransitions(); EnableTransitions();
if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer) if (PresentationSource.Renderer is IRendererWithCompositor compositingRenderer)
{ {
AttachToCompositor(compositingRenderer.Compositor); AttachToCompositor(compositingRenderer.Compositor);
} }
InvalidateMirrorTransform(); InvalidateMirrorTransform();
UpdateIsEffectivelyVisible(_visualParent.IsEffectivelyVisible); UpdateIsEffectivelyVisible(_visualParent?.IsEffectivelyVisible ?? true);
OnAttachedToVisualTree(e); OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e); AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual(); InvalidateVisual();
_visualRoot.Renderer.RecalculateChildren(_visualParent); if (_visualParent != null)
{
PresentationSource.Renderer.RecalculateChildren(_visualParent);
if (ZIndex != 0) if (ZIndex != 0)
_visualParent.HasNonUniformZIndexChildren = true; _visualParent.HasNonUniformZIndexChildren = true;
}
var visualChildren = VisualChildren; var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count; var visualChildrenCount = visualChildren.Count;
for (var i = 0; i < visualChildrenCount; i++) 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); child.OnAttachedToVisualTreeCore(e);
} }
@ -575,7 +576,6 @@ namespace Avalonia
{ {
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Detached from visual tree"); Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Detached from visual tree");
_visualRoot = this as IRenderRoot;
RootedVisualChildrenCount--; RootedVisualChildrenCount--;
if (RenderTransform is IMutableTransform mutableTransform) if (RenderTransform is IMutableTransform mutableTransform)
@ -589,7 +589,9 @@ namespace Avalonia
DetachFromCompositor(); DetachFromCompositor();
DetachedFromVisualTree?.Invoke(this, e); DetachedFromVisualTree?.Invoke(this, e);
e.Root.Renderer.AddDirty(this); PresentationSource?.Renderer.AddDirty(this);
PresentationSource = null;
var visualChildren = VisualChildren; var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count; var visualChildrenCount = visualChildren.Count;
@ -686,7 +688,7 @@ namespace Avalonia
parentVisual.HasNonUniformZIndexChildren = true; parentVisual.HasNonUniformZIndexChildren = true;
sender?.InvalidateVisual(); sender?.InvalidateVisual();
parent?.VisualRoot?.Renderer.RecalculateChildren(parent); parent?.PresentationSource?.Renderer.RecalculateChildren(parent);
} }
/// <summary> /// <summary>
@ -714,17 +716,15 @@ namespace Avalonia
var old = _visualParent; var old = _visualParent;
_visualParent = value; _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); OnDetachedFromVisualTreeCore(e);
} }
if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) if (_visualParent?.IsAttachedToVisualTree == true)
{ {
var root = this.FindAncestorOfType<IRenderRoot>() ?? var e = new VisualTreeAttachmentEventArgs(_visualParent, _visualParent.PresentationSource!);
throw new AvaloniaInternalException("Visual is atached to visual tree but root could not be found.");
var e = new VisualTreeAttachmentEventArgs(_visualParent, root);
OnAttachedToVisualTreeCore(e); OnAttachedToVisualTreeCore(e);
} }
@ -810,5 +810,26 @@ namespace Avalonia
HasMirrorTransform = shouldApplyMirrorTransform; 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> /// <returns>The point in client coordinates.</returns>
public static Point PointToClient(this Visual visual, PixelPoint point) 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)); throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
var rootPoint = root.PointToClient(point); var rootPoint = source.PointToClient(point);
return ((Visual)root).TranslatePoint(rootPoint, visual)!.Value; return root.TranslatePoint(rootPoint, visual)!.Value;
} }
/// <summary> /// <summary>
@ -30,10 +31,11 @@ namespace Avalonia
/// <returns>The point in screen coordinates.</returns> /// <returns>The point in screen coordinates.</returns>
public static PixelPoint PointToScreen(this Visual visual, Point point) 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)); throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual));
var p = visual.TranslatePoint(point, (Visual)root); var p = visual.TranslatePoint(point, root);
return root.PointToScreen(p!.Value); return source.PointToScreen(p!.Value);
} }
/// <summary> /// <summary>

42
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -331,9 +333,10 @@ namespace Avalonia.VisualTree
{ {
ThrowHelper.ThrowIfNull(visual, nameof(visual)); 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; return null;
} }
@ -342,7 +345,7 @@ namespace Avalonia.VisualTree
if (rootPoint.HasValue) if (rootPoint.HasValue)
{ {
return root.HitTester.HitTestFirst(rootPoint.Value, visual, filter); return source.HitTester.HitTestFirst(rootPoint.Value, visual, filter);
} }
return null; return null;
@ -380,14 +383,14 @@ namespace Avalonia.VisualTree
{ {
ThrowHelper.ThrowIfNull(visual, nameof(visual)); 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 Array.Empty<Visual>();
} }
return root.HitTester.HitTest(p, visual, filter); return source.HitTester.HitTest(p, visual, filter);
} }
/// <summary> /// <summary>
@ -456,19 +459,26 @@ namespace Avalonia.VisualTree
return visual.VisualParent as T; 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> /// <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> /// </summary>
/// <param name="visual">The visual.</param> public static ILayoutManager? GetLayoutManager(this Visual visual) =>
/// <returns> visual.PresentationSource?.LayoutRoot.LayoutManager;
/// The root visual or null if the visual is not rooted.
/// </returns>
public static IRenderRoot? GetVisualRoot(this Visual visual)
{
ThrowHelper.ThrowIfNull(visual, nameof(visual));
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> /// <summary>
/// Returns a value indicating whether this control is attached to a visual root. /// 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;
using System.Diagnostics;
using Avalonia.Rendering; using Avalonia.Rendering;
namespace Avalonia namespace Avalonia
@ -12,22 +13,37 @@ namespace Avalonia
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="VisualTreeAttachmentEventArgs"/> class. /// Initializes a new instance of the <see cref="VisualTreeAttachmentEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="parent">The parent that the visual is being attached to or detached from.</param> /// <param name="attachmentPoint">The parent that the visual's tree is being attached to or detached from.</param>
/// <param name="root">The root visual.</param> /// <param name="presentationSource">Presentation source this visual is being attached to.</param>
public VisualTreeAttachmentEventArgs(Visual parent, IRenderRoot root) public VisualTreeAttachmentEventArgs(Visual? attachmentPoint, IPresentationSource presentationSource)
{ {
Parent = parent ?? throw new ArgumentNullException(nameof(parent)); RootVisual = presentationSource.RootVisual ??
Root = root ?? throw new ArgumentNullException(nameof(root)); throw new InvalidOperationException("PresentationSource must have a non-null RootVisual.");
AttachmentPoint = attachmentPoint;
PresentationSource = presentationSource ?? throw new ArgumentNullException(nameof(presentationSource));
} }
/// <summary> /// <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> /// </summary>
public Visual Parent { get; } public Visual? AttachmentPoint { get; }
[Obsolete("Use " + nameof(AttachmentPoint))]
public Visual? Parent => AttachmentPoint;
/// <summary> /// <summary>
/// Gets the root of the visual tree that the visual is being attached to or detached from. /// Gets the root of the visual tree that the visual is being attached to or detached from.
/// </summary> /// </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 (IsDefault)
{ {
if (e.Root is IInputElement inputElement) if (e.RootVisual is IInputElement inputElement)
{ {
ListenForDefault(inputElement); ListenForDefault(inputElement);
} }
} }
if (IsCancel) if (IsCancel)
{ {
if (e.Root is IInputElement inputElement) if (e.RootVisual is IInputElement inputElement)
{ {
ListenForCancel(inputElement); ListenForCancel(inputElement);
} }
@ -229,14 +229,14 @@ namespace Avalonia.Controls
if (IsDefault) if (IsDefault)
{ {
if (e.Root is IInputElement inputElement) if (e.RootVisual is IInputElement inputElement)
{ {
StopListeningForDefault(inputElement); StopListeningForDefault(inputElement);
} }
} }
if (IsCancel) if (IsCancel)
{ {
if (e.Root is IInputElement inputElement) if (e.RootVisual is IInputElement inputElement)
{ {
StopListeningForCancel(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"); _captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons");
if (VisualRoot is Window window) if (TopLevel.GetTopLevel(this) is Window window)
{ {
_captionButtons?.Attach(window); _captionButtons?.Attach(window);
@ -67,7 +67,7 @@ namespace Avalonia.Controls.Chrome
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
if (VisualRoot is Window window) if (TopLevel.GetTopLevel(this) is Window window)
{ {
_disposables = new CompositeDisposable(6) _disposables = new CompositeDisposable(6)
{ {

2
src/Avalonia.Controls/Control.cs

@ -509,7 +509,7 @@ namespace Avalonia.Controls
if (e.Source == this if (e.Source == this
&& !e.Handled) && !e.Handled)
{ {
var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration.OpenContextMenu; var keymap = this.GetPlatformSettings()?.HotkeyConfiguration.OpenContextMenu;
if (keymap is null) if (keymap is null)
{ {

3
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -8,6 +8,7 @@ using Avalonia.Layout;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Avalonia.VisualTree;
namespace Avalonia.Controls 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 // 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. // template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid) if (!_presenter.IsMeasureValid)
(VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass(); this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup(); var deltaY = _presenter.GetOffsetForPopup();

3
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -7,6 +7,7 @@ using System;
using System.Globalization; using System.Globalization;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.VisualTree;
namespace Avalonia.Controls 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 // 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. // template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid) if (!_presenter.IsMeasureValid)
(VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass(); this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup(); var deltaY = _presenter.GetOffsetForPopup();

29
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@ -16,8 +16,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty = public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>(); Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>();
public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty = public static readonly StyledProperty<ExperimentalAcrylicMaterial?> MaterialProperty =
AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial>(nameof(Material)); AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial?>(nameof(Material));
private IDisposable? _subscription; private IDisposable? _subscription;
private IDisposable? _materialSubscription; private IDisposable? _materialSubscription;
@ -39,7 +39,7 @@ namespace Avalonia.Controls
set => SetValue(CornerRadiusProperty, value); set => SetValue(CornerRadiusProperty, value);
} }
public ExperimentalAcrylicMaterial Material public ExperimentalAcrylicMaterial? Material
{ {
get => GetValue(MaterialProperty); get => GetValue(MaterialProperty);
set => SetValue(MaterialProperty, value); set => SetValue(MaterialProperty, value);
@ -49,20 +49,29 @@ namespace Avalonia.Controls
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
var tl = (TopLevel)e.Root; var tl = TopLevel.GetTopLevel(this);
if (tl != null)
{
_subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty) _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
.Subscribe(x => .Subscribe(x =>
{ {
if (tl.PlatformImpl is null) if (tl.PlatformImpl is null || Material is null)
return; return;
if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None) if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None)
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel; Material.PlatformTransparencyCompensationLevel =
tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
else if (x == WindowTransparencyLevel.Blur) else if (x == WindowTransparencyLevel.Blur)
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel; Material.PlatformTransparencyCompensationLevel =
tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
else if (x == WindowTransparencyLevel.AcrylicBlur) 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(); UpdateMaterialSubscription();
} }
@ -86,7 +95,9 @@ namespace Avalonia.Controls
if (visual is CompositionExperimentalAcrylicVisual v) if (visual is CompositionExperimentalAcrylicVisual v)
{ {
v.CornerRadius = CornerRadius; 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; 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 // As long as the pointer stays within the enlargedPopupRect
// the flyout stays open. If it leaves, close it // 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(); // DpiScale dpiScale = GetDpi();
// double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; // 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[] roundingErrors = RoundingErrors;
double roundedTakenSize = 0; double roundedTakenSize = 0;

3
src/Avalonia.Controls/GridSplitter.cs

@ -13,6 +13,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -226,7 +227,7 @@ namespace Avalonia.Controls
ResizeDirection = resizeDirection, ResizeDirection = resizeDirection,
SplitterLength = Math.Min(Bounds.Width, Bounds.Height), SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection), ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection),
Scaling = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1, Scaling = this.GetLayoutRoot()?.LayoutScaling ?? 1,
}; };
// Store the rows and columns to resize on drag events. // Store the rows and columns to resize on drag events.

2
src/Avalonia.Controls/IMenu.cs

@ -23,6 +23,6 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the root of the visual tree, if the control is attached to a visual tree. /// Gets the root of the visual tree, if the control is attached to a visual tree.
/// </summary> /// </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) 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); var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
if (!ctrl && if (!ctrl &&

3
src/Avalonia.Controls/MaskedTextBox.cs

@ -6,6 +6,7 @@ using System.Linq;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -203,7 +204,7 @@ namespace Avalonia.Controls
return; return;
} }
var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration; var keymap = this.GetPlatformSettings()?.HotkeyConfiguration;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e)); 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); base.OnAttachedToVisualTree(e);
var inputRoot = e.Root as TopLevel; var inputRoot = TopLevel.GetTopLevel(this);
if (inputRoot?.AccessKeyHandler != null) if (inputRoot?.AccessKeyHandler != null)
{ {

2
src/Avalonia.Controls/MenuBase.cs

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

2
src/Avalonia.Controls/MenuItemAccessKeyHandler.cs

@ -9,7 +9,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
internal class MenuItemAccessKeyHandler : AccessKeyHandler internal class MenuItemAccessKeyHandler : AccessKeyHandler
{ {
protected override void OnSetOwner(IInputRoot owner) protected override void OnSetOwner(InputElement owner)
{ {
owner.AddHandler(InputElement.TextInputEvent, OnTextInput); 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.Automation.Peers;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -12,7 +13,7 @@ namespace Avalonia.Controls
{ {
public class NativeControlHost : Control public class NativeControlHost : Control
{ {
private TopLevel? _currentRoot; private PresentationSource? _currentRoot;
private INativeControlHostImpl? _currentHost; private INativeControlHostImpl? _currentHost;
private INativeControlHostControlTopLevelAttachment? _attachment; private INativeControlHostControlTopLevelAttachment? _attachment;
private IPlatformHandle? _nativeControlHandle; private IPlatformHandle? _nativeControlHandle;
@ -43,7 +44,7 @@ namespace Avalonia.Controls
/// <inheritdoc /> /// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = e.Root as TopLevel; _currentRoot = (PresentationSource)e.PresentationSource;
var visual = (Visual)this; var visual = (Visual)this;
while (visual != null) while (visual != null)
{ {
@ -147,7 +148,7 @@ namespace Avalonia.Controls
var bounds = Bounds; var bounds = Bounds;
// Native window is not rendered by Avalonia // 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) if (transformToVisual == null)
return null; return null;
var position = new Rect(default, bounds.Size).TransformToAABB(transformToVisual.Value).Position; 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>() ?? this.FindDescendantOfType<MenuBase>()
?? throw new InvalidOperationException("NativeMenuBar requires a MenuBase#PART_NativeMenuPresenter template part."); ?? 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); SubscribeToToplevel(topLevel, _menu);
} }
@ -47,7 +47,7 @@ namespace Avalonia.Controls
if (_menu is null) if (_menu is null)
return; return;
if (e.Root is TopLevel topLevel) if (TopLevel.GetTopLevel(this) is {} topLevel)
{ {
SubscribeToToplevel(topLevel, _menu); SubscribeToToplevel(topLevel, _menu);
} }

4
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -21,7 +21,7 @@ namespace Avalonia.Controls.Platform
{ {
private readonly bool _isContextMenu; private readonly bool _isContextMenu;
private IDisposable? _inputManagerSubscription; private IDisposable? _inputManagerSubscription;
private IRenderRoot? _root; private TopLevel? _root;
private RadioButtonGroupManager? _groupManager; private RadioButtonGroupManager? _groupManager;
public DefaultMenuInteractionHandler(bool isContextMenu) public DefaultMenuInteractionHandler(bool isContextMenu)
@ -300,7 +300,7 @@ namespace Avalonia.Controls.Platform
Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved); Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
_root = Menu.VisualRoot; _root = Menu.TopLevel;
if (_root is not null) if (_root is not null)
{ {

37
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -19,7 +19,7 @@ namespace Avalonia.Platform
private DragDropEffects _allowedEffects; private DragDropEffects _allowedEffects;
private IDataTransfer? _draggedData; private IDataTransfer? _draggedData;
private TopLevel? _lastRoot; private PresentationSource? _lastSource;
private Point _lastPosition; private Point _lastPosition;
private StandardCursorType? _lastCursorType; private StandardCursorType? _lastCursorType;
private RawInputModifiers? _initialInputModifiers; private RawInputModifiers? _initialInputModifiers;
@ -40,7 +40,7 @@ namespace Avalonia.Platform
if (_draggedData == null) if (_draggedData == null)
{ {
_draggedData = dataTransfer; _draggedData = dataTransfer;
_lastRoot = null; _lastSource = null;
_lastPosition = default; _lastPosition = default;
_allowedEffects = allowedEffects; _allowedEffects = allowedEffects;
@ -75,11 +75,12 @@ namespace Avalonia.Platform
_lastPosition = pt; _lastPosition = pt;
RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData!, _allowedEffects, modifiers); RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData!, _allowedEffects, modifiers);
var tl = (root as Visual)?.GetSelfAndVisualAncestors().OfType<TopLevel>().FirstOrDefault(); var source = root.RootElement.PresentationSource as PresentationSource;
tl?.PlatformImpl?.Input?.Invoke(rawEvent);
source?.PlatformImpl?.Input?.Invoke(rawEvent);
var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers); var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
UpdateCursor(tl, effect); UpdateCursor(source, effect);
return effect; return effect;
} }
@ -105,12 +106,12 @@ namespace Avalonia.Platform
return StandardCursorType.No; 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); _lastSource?.SetCursorOverride(null);
_lastRoot = root; _lastSource = root;
_lastCursorType = null; _lastCursorType = null;
} }
@ -127,8 +128,8 @@ namespace Avalonia.Platform
private void CancelDragging() private void CancelDragging()
{ {
if (_lastRoot != null) if (_lastSource != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, RawInputModifiers.None); RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, _lastPosition, RawInputModifiers.None);
UpdateCursor(null, DragDropEffects.None); UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None); _result.OnNext(DragDropEffects.None);
} }
@ -137,16 +138,16 @@ namespace Avalonia.Platform
{ {
if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape) if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape)
{ {
if (_lastRoot != null) if (_lastSource != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers); RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, _lastPosition, e.Modifiers);
UpdateCursor(null, DragDropEffects.None); UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None); _result.OnNext(DragDropEffects.None);
e.Handled = true; e.Handled = true;
} }
else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt) else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
{ {
if (_lastRoot != null) if (_lastSource != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers); RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastSource, _lastPosition, e.Modifiers);
} }
} }
@ -195,10 +196,10 @@ namespace Avalonia.Platform
return; return;
} }
if (e.Root != _lastRoot) if (e.Root != _lastSource)
{ {
if (_lastRoot is Visual lr && e.Root is Visual r) if (_lastSource?.RootElement is Visual lr && e.Root.RootElement is Visual r)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, lr.PointToClient(r.PointToScreen(e.Position)), e.InputModifiers); RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastSource, lr.PointToClient(r.PointToScreen(e.Position)), e.InputModifiers);
RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers); RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers);
} }
else 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. /// Defines the <see cref="ShowAccessKey"/> attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<bool> ShowAccessKeyProperty = public static readonly AttachedProperty<bool> ShowAccessKeyProperty =
AvaloniaProperty.RegisterAttached<AccessText, Control, bool>("ShowAccessKey", inherits: true); AccessKeyHandler.ShowAccessKeyProperty.AddOwner<AccessText>();
/// <summary> /// <summary>
/// The access key handler for the current window. /// The access key handler for the current window.
@ -92,7 +92,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
_accessKeys = (e.Root as TopLevel)?.AccessKeyHandler; _accessKeys = TopLevel.GetTopLevel(this)?.AccessKeyHandler;
if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey)) if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{ {

3
src/Avalonia.Controls/Primitives/ItemSelectionEventTriggers.cs

@ -1,6 +1,7 @@
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives; 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); public static bool HasToggleSelectionModifier(Visual selectable, RoutedEventArgs eventArgs) => HasModifiers(eventArgs, Hotkeys(selectable)?.CommandModifiers);
private static PlatformHotkeyConfiguration? Hotkeys(Visual element) => 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) => private static bool HasModifiers(RoutedEventArgs eventArgs, KeyModifiers? modifiers) =>
modifiers != null && eventArgs is IKeyModifiersEventArgs { KeyModifiers: { } eventModifiers } && eventModifiers.HasAllFlags(modifiers.Value); 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.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -10,7 +11,7 @@ using Avalonia.Platform;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
public class OverlayPopupHost : ContentControl, IPopupHost, IManagedPopupPositionerPopup, IInputRoot public class OverlayPopupHost : ContentControl, IPopupHost, IManagedPopupPositionerPopup
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Transform"/> property. /// Defines the <see cref="Transform"/> property.
@ -21,6 +22,7 @@ namespace Avalonia.Controls.Primitives
private readonly OverlayLayer _overlayLayer; private readonly OverlayLayer _overlayLayer;
private readonly ManagedPopupPositioner _positioner; private readonly ManagedPopupPositioner _positioner;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
internal IKeyboardNavigationHandler Tests_KeyboardNavigationHandler => _keyboardNavigationHandler!;
private Point _lastRequestedPosition; private Point _lastRequestedPosition;
private PopupPositionRequest? _popupPositionRequest; private PopupPositionRequest? _popupPositionRequest;
private Size _popupSize; private Size _popupSize;
@ -60,38 +62,6 @@ namespace Avalonia.Controls.Primitives
set { /* Not currently supported in overlay popups */ } 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 /> /// <inheritdoc />
internal override Interactive? InteractiveParent => Parent as Interactive; 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>(); 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(); Close();
return; return;

2
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -574,7 +574,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
if (matrix == null) 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 attached to the visual tree");
throw new InvalidOperationException("Target control is not in the same tree as the popup parent"); 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.Automation.Peers;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;

6
src/Avalonia.Controls/RadioButton.cs

@ -52,7 +52,7 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_groupManager?.Remove(this, GroupName); _groupManager?.Remove(this, GroupName);
EnsureRadioGroupManager(e.Root); EnsureRadioGroupManager(e.PresentationSource);
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
} }
@ -106,9 +106,9 @@ namespace Avalonia.Controls
} }
[MemberNotNull(nameof(_groupManager))] [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); _groupManager.Add(this);
} }
} }

4
src/Avalonia.Controls/RadioButtonGroupManager.cs

@ -18,12 +18,12 @@ internal interface IRadioButton : ILogical
internal class RadioButtonGroupManager internal class RadioButtonGroupManager
{ {
private static readonly RadioButtonGroupManager s_default = new(); 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 readonly Dictionary<string, List<WeakReference<IRadioButton>>> _registeredGroups = new();
private bool _ignoreCheckedChanges; private bool _ignoreCheckedChanges;
public static RadioButtonGroupManager GetOrCreateForRoot(IRenderRoot? root) public static RadioButtonGroupManager GetOrCreateForRoot(object? root)
{ {
if (root == null) if (root == null)
return s_default; 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) protected virtual Size Measure(Size constraint)
{ {
var l = (Layoutable) InputRoot!; var l = InputRoot!.RootElement;
l.Measure(constraint); l.Measure(constraint);
return l.DesiredSize; return l.DesiredSize;
} }

6
src/Avalonia.Controls/ToolTipService.cs

@ -38,12 +38,12 @@ namespace Avalonia.Controls
if (e is RawPointerEventArgs pointerEvent) if (e is RawPointerEventArgs pointerEvent)
{ {
bool isTooltipEvent = false; 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; isTooltipEvent = true;
_lastTipEventTime = pointerEvent.Timestamp; _lastTipEventTime = pointerEvent.Timestamp;
} }
else if (e.Root == _tipControl?.VisualRoot) else if (e.Root.RootElement == _tipControl?.VisualRoot)
{ {
_lastWindowEventTime = pointerEvent.Timestamp; _lastWindowEventTime = pointerEvent.Timestamp;
} }
@ -53,7 +53,7 @@ namespace Avalonia.Controls
case RawPointerEventType.Move: case RawPointerEventType.Move:
Update(pointerEvent.Root, pointerEvent.InputHitTestResult.element as Visual); Update(pointerEvent.Root, pointerEvent.InputHitTestResult.element as Visual);
break; 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(); ClearTip();
_tipControl = null; _tipControl = null;
break; break;

296
src/Avalonia.Controls/TopLevel.cs

@ -39,13 +39,9 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
[TemplatePart("PART_TransparencyFallback", typeof(Border))] [TemplatePart("PART_TransparencyFallback", typeof(Border))]
public abstract class TopLevel : ContentControl, public abstract class TopLevel : ContentControl,
IInputRoot,
ILayoutRoot,
IRenderRoot,
ICloseable, ICloseable,
IStyleHost, IStyleHost,
ILogicalRoot, ILogicalRoot
ITextInputMethodRoot
{ {
/// <summary> /// <summary>
/// Defines the <see cref="ClientSize"/> property. /// Defines the <see cref="ClientSize"/> property.
@ -59,12 +55,6 @@ namespace Avalonia.Controls
public static readonly DirectProperty<TopLevel, Size?> FrameSizeProperty = public static readonly DirectProperty<TopLevel, Size?> FrameSizeProperty =
AvaloniaProperty.RegisterDirect<TopLevel, Size?>(nameof(FrameSize), o => o.FrameSize); 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> /// <summary>
/// Defines the <see cref="TransparencyLevelHint"/> property. /// Defines the <see cref="TransparencyLevelHint"/> property.
/// </summary> /// </summary>
@ -125,24 +115,22 @@ namespace Avalonia.Controls
private readonly IToolTipService? _tooltipService; private readonly IToolTipService? _tooltipService;
private readonly IAccessKeyHandler? _accessKeyHandler; private readonly IAccessKeyHandler? _accessKeyHandler;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
internal IKeyboardNavigationHandler Tests_KeyboardNavigationHandler => _keyboardNavigationHandler!;
private readonly IGlobalStyles? _globalStyles; private readonly IGlobalStyles? _globalStyles;
private readonly IThemeVariantHost? _applicationThemeHost; private readonly IThemeVariantHost? _applicationThemeHost;
private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
private readonly IDisposable? _pointerOverPreProcessorSubscription;
private readonly IDisposable? _backGestureSubscription; private readonly IDisposable? _backGestureSubscription;
private readonly Dictionary<AvaloniaProperty, Action> _platformImplBindings = new(); private readonly Dictionary<AvaloniaProperty, Action> _platformImplBindings = new();
private double _scaling; private double _scaling;
private Size _clientSize; private Size _clientSize;
private Size? _frameSize; private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel; private WindowTransparencyLevel _actualTransparencyLevel;
private ILayoutManager? _layoutManager;
private Border? _transparencyFallbackBorder; private Border? _transparencyFallbackBorder;
private TargetWeakEventSubscriber<TopLevel, ResourcesChangedEventArgs>? _resourcesChangesSubscriber; private TargetWeakEventSubscriber<TopLevel, ResourcesChangedEventArgs>? _resourcesChangesSubscriber;
private IStorageProvider? _storageProvider; private IStorageProvider? _storageProvider;
private Screens? _screens; private Screens? _screens;
private LayoutDiagnosticBridge? _layoutDiagnosticBridge; private readonly PresentationSource _source;
private Cursor? _cursor; internal new PresentationSource PresentationSource => _source;
private Cursor? _cursorOverride; internal IInputRoot InputRoot => _source;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TopLevel"/> class. /// Initializes static members of the <see cref="TopLevel"/> class.
@ -176,19 +164,6 @@ namespace Avalonia.Controls
topLevel?.InvalidateChildInsetsPadding(); 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); ToolTip.ServiceEnabledProperty.Changed.Subscribe(OnToolTipServiceEnabledChanged);
} }
@ -209,15 +184,20 @@ namespace Avalonia.Controls
/// <param name="dependencyResolver"> /// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used. /// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param> /// </param>
public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver) internal TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver)
{ {
PlatformImpl = impl ?? throw new InvalidOperationException( PlatformImpl = impl ?? throw new InvalidOperationException(
"Could not create window implementation: maybe no windowing subsystem was initialized?"); "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); _scaling = ValidateScaling(impl.RenderScaling);
_actualTransparencyLevel = PlatformImpl.TransparencyLevel; _actualTransparencyLevel = PlatformImpl.TransparencyLevel;
dependencyResolver ??= AvaloniaLocator.Current;
_accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver); _accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
_inputManager = TryGetService<IInputManager>(dependencyResolver); _inputManager = TryGetService<IInputManager>(dependencyResolver);
@ -226,13 +206,10 @@ namespace Avalonia.Controls
_globalStyles = TryGetService<IGlobalStyles>(dependencyResolver); _globalStyles = TryGetService<IGlobalStyles>(dependencyResolver);
_applicationThemeHost = TryGetService<IThemeVariantHost>(dependencyResolver); _applicationThemeHost = TryGetService<IThemeVariantHost>(dependencyResolver);
Renderer = new CompositingRenderer(this, impl.Compositor, () => PlatformImpl.Surfaces ?? []);
Renderer.SceneInvalidated += SceneInvalidated;
impl.SetInputRoot(this);
impl.Closed = HandleClosed; impl.Closed = HandleClosed;
impl.Input = HandleInput;
impl.Paint = HandlePaint; impl.Paint = HandlePaint;
impl.Resized = HandleResized; impl.Resized = HandleResized;
impl.ScalingChanged = HandleScalingChanged; impl.ScalingChanged = HandleScalingChanged;
@ -276,8 +253,7 @@ namespace Avalonia.Controls
impl.LostFocus += PlatformImpl_LostFocus; impl.LostFocus += PlatformImpl_LostFocus;
_pointerOverPreProcessor = new PointerOverPreProcessor(this);
_pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
if(impl.TryGetFeature<ISystemNavigationManagerImpl>() is {} systemNavigationManager) if(impl.TryGetFeature<ISystemNavigationManagerImpl>() is {} systemNavigationManager)
{ {
@ -407,26 +383,7 @@ namespace Avalonia.Controls
remove => RemoveHandler(BackRequestedEvent, value); remove => RemoveHandler(BackRequestedEvent, value);
} }
internal ILayoutManager LayoutManager internal ILayoutManager LayoutManager => _source.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;
/// <summary> /// <summary>
/// Gets the platform-specific window implementation. /// Gets the platform-specific window implementation.
@ -461,24 +418,23 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the renderer for the window. /// Gets the renderer for the window.
/// </summary> /// </summary>
internal CompositingRenderer Renderer { get; } internal CompositingRenderer Renderer => _source.Renderer;
internal IHitTester HitTester => HitTesterOverride ?? Renderer;
// This property setter is here purely for lazy unit tests // This property setter is here purely for lazy unit tests
// that don't want to set up a proper hit-testable visual tree // that don't want to set up a proper hit-testable visual tree
// and should be removed after fixing those tests // and should be removed after fixing those tests
internal IHitTester? HitTesterOverride; internal IHitTester? HitTesterOverride
{
IRenderer IRenderRoot.Renderer => Renderer; get => _source.HitTesterOverride;
IHitTester IRenderRoot.HitTester => HitTester; set => _source.HitTesterOverride = value;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the renderer should draw specific diagnostics. /// Gets a value indicating whether the renderer should draw specific diagnostics.
/// </summary> /// </summary>
public RendererDiagnostics RendererDiagnostics => Renderer.Diagnostics; public RendererDiagnostics RendererDiagnostics => Renderer.Diagnostics;
internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition; internal PixelPoint? LastPointerPosition => _source.GetLastPointerPosition(this);
/// <summary> /// <summary>
/// Gets the access key handler for the window. /// Gets the access key handler for the window.
@ -488,23 +444,6 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the keyboard navigation handler for the window. /// Gets or sets the keyboard navigation handler for the window.
/// </summary> /// </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> /// <summary>
/// Helper for setting the color of the platform's system bars. /// Helper for setting the color of the platform's system bars.
@ -545,8 +484,6 @@ namespace Avalonia.Controls
return control.GetValue(AutoSafeAreaPaddingProperty); return control.GetValue(AutoSafeAreaPaddingProperty);
} }
double ILayoutRoot.LayoutScaling => _scaling;
/// <inheritdoc/> /// <inheritdoc/>
public double RenderScaling => _scaling; public double RenderScaling => _scaling;
@ -575,23 +512,22 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>(); public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>();
/// <inheritdoc /> /// <summary>
public IFocusManager? FocusManager => _focusManager ??= new FocusManager(this); /// Gets focus manager of the root.
/// </summary>
/// <inheritdoc /> /// <remarks>
public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>(); /// Focus manager can be null only if window wasn't initialized yet.
/// </remarks>
/// <inheritdoc/> public IFocusManager FocusManager => _source.FocusManager;
Point IRenderRoot.PointToClient(PixelPoint p)
{
return PlatformImpl?.PointToClient(p) ?? default;
}
/// <inheritdoc/> /// <summary>
PixelPoint IRenderRoot.PointToScreen(Point p) /// Represents a contract for accessing top-level platform-specific settings.
{ /// </summary>
return PlatformImpl?.PointToScreen(p) ?? default; /// <remarks>
} /// PlatformSettings can be null only if window wasn't initialized yet.
/// </remarks>
// TODO: Un-private
private IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
/// <summary> /// <summary>
/// Gets the <see cref="TopLevel" /> for which the given <see cref="Visual"/> is hosted in. /// 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> /// <returns>The TopLevel</returns>
public static TopLevel? GetTopLevel(Visual? visual) 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> /// <summary>
@ -651,7 +594,6 @@ namespace Avalonia.Controls
} }
private IDisposable? _insetsPaddings; private IDisposable? _insetsPaddings;
private FocusManager? _focusManager;
private void InvalidateChildInsetsPadding() 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> /// <summary>
/// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>. /// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary> /// </summary>
@ -691,18 +628,16 @@ namespace Avalonia.Controls
Renderer.Paint(rect); 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> /// <summary>
/// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>. /// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>.
/// </summary> /// </summary>
private protected virtual void HandleClosed() private protected virtual void HandleClosed()
{ {
Renderer.SceneInvalidated -= SceneInvalidated; _source.Dispose();
// We need to wait for the renderer to complete any in-flight operations
Renderer.Dispose();
StopRendering(); StopRendering();
Debug.Assert(PlatformImpl != null); Debug.Assert(PlatformImpl != null);
@ -720,18 +655,12 @@ namespace Avalonia.Controls
_applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged;
} }
_layoutDiagnosticBridge?.Dispose();
_layoutDiagnosticBridge = null;
_pointerOverPreProcessor?.OnCompleted();
_pointerOverPreProcessorSubscription?.Dispose();
_backGestureSubscription?.Dispose(); _backGestureSubscription?.Dispose();
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
var visualArgs = new VisualTreeAttachmentEventArgs(this, this); _source.RootVisual = null!;
OnDetachedFromVisualTreeCore(visualArgs);
OnClosed(EventArgs.Empty); OnClosed(EventArgs.Empty);
@ -784,15 +713,6 @@ namespace Avalonia.Controls
ActualTransparencyLevel = transparencyLevel; 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) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
@ -844,73 +764,6 @@ namespace Avalonia.Controls
return result; 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) private void GlobalActualThemeVariantChanged(object? sender, EventArgs e)
{ {
SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template); SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template);
@ -918,7 +771,6 @@ namespace Avalonia.Controls
private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e)
{ {
_pointerOverPreProcessor?.SceneInvalidated(e.DirtyRect);
UpdateToolTip(e.DirtyRect); UpdateToolTip(e.DirtyRect);
} }
@ -935,12 +787,12 @@ namespace Avalonia.Controls
private void UpdateToolTip(Rect dirtyRect) 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); var clientPoint = this.PointToClient(lastPos);
if (dirtyRect.Contains(clientPoint)) 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. // Do nothing becuase TopLevel should't apply MirrorTransform on himself.
} }
ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => PlatformImpl?.TryGetFeature<ITextInputMethodImpl>();
private double ValidateScaling(double scaling) private double ValidateScaling(double scaling)
{ {
if (MathUtilities.IsNegativeOrNonFinite(scaling) || MathUtilities.IsZero(scaling)) if (MathUtilities.IsNegativeOrNonFinite(scaling) || MathUtilities.IsZero(scaling))
@ -983,49 +833,5 @@ namespace Avalonia.Controls
return scaling; 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; item.IsExpanded = true;
if (item.Presenter?.Panel is null) if (item.Presenter?.Panel is null)
(this.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); this.GetLayoutManager()?.ExecuteLayoutPass();
if (item.Presenter?.Panel is { } panel) if (item.Presenter?.Panel is { } panel)
{ {

2
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -459,7 +459,7 @@ namespace Avalonia.Controls
element.BringIntoView(); element.BringIntoView();
return element; 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 // 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. // it can be re-used in the layout pass.

2
src/Avalonia.Controls/Window.cs

@ -86,7 +86,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A top-level window. /// A top-level window.
/// </summary> /// </summary>
public class Window : WindowBase, IFocusScope, ILayoutRoot public class Window : WindowBase, IFocusScope
{ {
private static readonly Lazy<WindowIcon?> s_defaultIcon = new(LoadDefaultIcon); private static readonly Lazy<WindowIcon?> s_defaultIcon = new(LoadDefaultIcon);
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); 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() 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) 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.Input.TextInput;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using MicroCom.Runtime; using MicroCom.Runtime;
namespace Avalonia.Native namespace Avalonia.Native
@ -136,8 +137,9 @@ namespace Avalonia.Native
{ {
if(e.Type == RawPointerEventType.LeftButtonDown) if(e.Type == RawPointerEventType.LeftButtonDown)
{ {
var window = _inputRoot as Window; // TODO: Casts like this are evil
var visual = window?.Renderer.HitTestFirst(e.Position, window, x => var source = (PresentationSource?)_inputRoot;
var visual = source?.Renderer.HitTestFirst(e.Position, source.RootElement, x =>
{ {
if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) 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) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
_compositor = (this.GetVisualRoot()?.Renderer as IRendererWithCompositor)?.Compositor; _compositor = (this.GetPresentationSource()?.Renderer as IRendererWithCompositor)?.Compositor;
RequestNextFrameRendering(); RequestNextFrameRendering();
} }
@ -201,11 +201,11 @@ namespace Avalonia.OpenGL.Controls
private void Update() private void Update()
{ {
_updateQueued = false; _updateQueued = false;
if (VisualRoot is not { } visualRoot) if (this.GetPresentationSource() is not { } source)
return; return;
if(!EnsureInitialized()) if(!EnsureInitialized())
return; return;
using (_resources.BeginDraw(GetPixelSize(visualRoot))) using (_resources.BeginDraw(GetPixelSize(source)))
OnOpenGlRender(_resources.Context.GlInterface, _resources.Fbo); 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)), return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)),
Math.Max(1, (int)(Bounds.Height * 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.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Themes.Fluent.Accents; namespace Avalonia.Themes.Fluent.Accents;
@ -120,7 +121,7 @@ internal sealed class SystemAccentColors : ResourceProvider
return owner switch return owner switch
{ {
Application app => app.PlatformSettings, Application app => app.PlatformSettings,
Visual visual => TopLevel.GetTopLevel(visual)?.PlatformSettings, Visual visual => visual.GetPlatformSettings(),
_ => null _ => null
}; };
} }

4
src/Avalonia.X11/X11Window.cs

@ -267,7 +267,9 @@ namespace Avalonia.X11
? DBusSystemDialog.TryCreateAsync(Handle) ? DBusSystemDialog.TryCreateAsync(Handle)
: Task.FromResult<IStorageProvider?>(null), : Task.FromResult<IStorageProvider?>(null),
() => GtkSystemDialog.TryCreate(this), () => 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) ? (IStorageProvider?)new ManagedStorageProvider(tl)
: null) : null)
}); });

4
src/Avalonia.X11/X11WindowModes/XEmbedClientWindowMode.cs

@ -9,7 +9,7 @@ partial class X11Window
{ {
public class XEmbedClientWindowMode : X11WindowMode public class XEmbedClientWindowMode : X11WindowMode
{ {
EmbeddableControlRoot? Root => Window._inputRoot as EmbeddableControlRoot; EmbeddableControlRoot? Root => Window._inputRoot?.RootElement as EmbeddableControlRoot;
private bool _focusedInEmbedder; private bool _focusedInEmbedder;
private bool _embedderActivated; private bool _embedderActivated;
private bool _disabled; private bool _disabled;
@ -79,7 +79,7 @@ partial class X11Window
if (args.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) if (args.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{ {
if (KeyboardDevice.Instance.FocusedElement is Visual visual if (KeyboardDevice.Instance.FocusedElement is Visual visual
&& visual.VisualRoot is EmbeddableControlRoot root && TopLevel.GetTopLevel(visual) is EmbeddableControlRoot root
&& root.PlatformImpl is X11Window window && root.PlatformImpl is X11Window window
&& window._mode is XEmbedClientWindowMode xembedMode && window._mode is XEmbedClientWindowMode xembedMode
&& xembedMode._currentEmbedder != IntPtr.Zero) && xembedMode._currentEmbedder != IntPtr.Zero)

2
src/Windows/Avalonia.Win32/OleDropTarget.cs

@ -209,7 +209,7 @@ namespace Avalonia.Win32
private Point GetDragLocation(UnmanagedMethods.POINT dragPoint) private Point GetDragLocation(UnmanagedMethods.POINT dragPoint)
{ {
var screenPt = new PixelPoint(dragPoint.X, dragPoint.Y); var screenPt = new PixelPoint(dragPoint.X, dragPoint.Y);
return ((Visual)_target).PointToClient(screenPt); return _target.RootElement.PointToClient(screenPt);
} }
protected override void Destroyed() protected override void Destroyed()

2
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -937,7 +937,7 @@ namespace Avalonia.Win32
return IntPtr.Zero; return IntPtr.Zero;
} }
case WindowsMessage.WM_GETOBJECT: 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 peer = ControlAutomationPeer.CreatePeerForElement(control);
var node = AutomationNode.GetOrCreate(peer); 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) private HitTestValues HitTestVisual(IntPtr lParam)
{ {
var position = PointToClient(PointFromLParam(lParam)); var position = PointToClient(PointFromLParam(lParam));
if (_owner is Window window) if (_owner?.RootElement is {} window)
{ {
var visual = window.GetVisualAt(position, x => var visual = window.GetVisualAt(position, x =>
{ {

46
tests/Avalonia.Base.UnitTests/Input/KeyboardDeviceTests.cs

@ -12,21 +12,28 @@ namespace Avalonia.Base.UnitTests.Input
[Fact] [Fact]
public void Keypresses_Should_Be_Sent_To_Root_If_No_Focused_Element() public void Keypresses_Should_Be_Sent_To_Root_If_No_Focused_Element()
{ {
var target = new KeyboardDevice(); using (UnitTestApplication.Start(TestServices.FocusableWindow))
var root = new Mock<IInputRoot>(); {
var window = new Window();
target.ProcessRawEvent( window.FocusManager.ClearFocus();
int raised = 0;
window.KeyDown += (sender, ev) =>
{
if (sender == window && ev.RoutedEvent == InputElement.KeyDownEvent)
raised++;
};
KeyboardDevice.Instance!.ProcessRawEvent(
new RawKeyEventArgs( new RawKeyEventArgs(
target, KeyboardDevice.Instance,
0, 0,
root.Object, window.GetInputRoot()!,
RawKeyEventType.KeyDown, RawKeyEventType.KeyDown,
Key.A, Key.A,
RawInputModifiers.None, RawInputModifiers.None,
PhysicalKey.A, PhysicalKey.A,
"a")); "a"));
Assert.Equal(1, raised);
root.Verify(x => x.RaiseEvent(It.IsAny<KeyEventArgs>())); }
} }
[Fact] [Fact]
@ -61,17 +68,24 @@ namespace Avalonia.Base.UnitTests.Input
[Fact] [Fact]
public void TextInput_Should_Be_Sent_To_Root_If_No_Focused_Element() public void TextInput_Should_Be_Sent_To_Root_If_No_Focused_Element()
{ {
var target = new KeyboardDevice(); using (UnitTestApplication.Start(TestServices.FocusableWindow))
var root = new Mock<IInputRoot>(); {
var window = new Window();
target.ProcessRawEvent( window.FocusManager.ClearFocus();
int raised = 0;
window.TextInput += (sender, ev) =>
{
if (sender == window && ev.RoutedEvent == InputElement.TextInputEvent)
raised++;
};
KeyboardDevice.Instance!.ProcessRawEvent(
new RawTextInputEventArgs( new RawTextInputEventArgs(
target, KeyboardDevice.Instance,
0, 0,
root.Object, window.GetInputRoot()!,
"Foo")); "Foo"));
Assert.Equal(1, raised);
root.Verify(x => x.RaiseEvent(It.IsAny<TextInputEventArgs>())); }
} }
[Fact] [Fact]

6
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@ -246,12 +246,12 @@ namespace Avalonia.Base.UnitTests.Input
// Enter decorator // Enter decorator
SetHit(renderer, decorator); SetHit(renderer, decorator);
SetMove(deviceMock, root, decorator); SetMove(deviceMock, root.InputRoot, decorator);
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root));
// Leave decorator // Leave decorator
SetHit(renderer, canvas); SetHit(renderer, canvas);
SetMove(deviceMock, root, canvas); SetMove(deviceMock, root.InputRoot, canvas);
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root));
Assert.Equal( Assert.Equal(
@ -491,7 +491,7 @@ namespace Avalonia.Base.UnitTests.Input
Assert.True(canvas.IsPointerOver); Assert.True(canvas.IsPointerOver);
// Send LeaveWindow. // 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.False(canvas.IsPointerOver);
Assert.Equal( Assert.Equal(

10
tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs

@ -78,26 +78,26 @@ public abstract class PointerTestsBase : ScopedTestBase
protected static RawPointerEventArgs CreateRawPointerArgs( protected static RawPointerEventArgs CreateRawPointerArgs(
IPointerDevice pointerDevice, IPointerDevice pointerDevice,
IInputRoot root, TopLevel root,
RawPointerEventType type, RawPointerEventType type,
Point? position = default) 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( protected static RawPointerEventArgs CreateRawPointerMovedArgs(
IPointerDevice pointerDevice, IPointerDevice pointerDevice,
IInputRoot root, TopLevel root,
Point? position = null) Point? position = null)
{ {
return new RawPointerEventArgs(pointerDevice, 0, root, RawPointerEventType.Move, return new RawPointerEventArgs(pointerDevice, 0, root.PresentationSource, RawPointerEventType.Move,
position ?? default, default); position ?? default, default);
} }
protected static PointerEventArgs CreatePointerMovedArgs( protected static PointerEventArgs CreatePointerMovedArgs(
IInputRoot root, IInputElement? source, Point? position = null) 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); 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) => child1.AttachedToVisualTree += (s, e) =>
{ {
Assert.Equal(e.Parent, root); // TODO: Tests are running against TestRoot, so behavior DOES NOT match the actual TopLevel.
Assert.Equal(e.Root, root); Assert.Equal(e.AttachmentPoint, root);
Assert.Equal(e.RootVisual, root);
called1 = true; called1 = true;
}; };
child2.AttachedToVisualTree += (s, e) => child2.AttachedToVisualTree += (s, e) =>
{ {
Assert.Equal(e.Parent, root); Assert.Equal(e.AttachmentPoint, root);
Assert.Equal(e.Root, root); Assert.Equal(e.RootVisual, root);
called2 = true; called2 = true;
}; };
@ -107,15 +108,16 @@ namespace Avalonia.Base.UnitTests
child1.DetachedFromVisualTree += (s, e) => child1.DetachedFromVisualTree += (s, e) =>
{ {
Assert.Equal(e.Parent, root); // TODO: Tests are running against TestRoot, so behavior DOES NOT match the actual TopLevel.
Assert.Equal(e.Root, root); Assert.Equal(e.AttachmentPoint, root);
Assert.Equal(e.RootVisual, root);
called1 = true; called1 = true;
}; };
child2.DetachedFromVisualTree += (s, e) => child2.DetachedFromVisualTree += (s, e) =>
{ {
Assert.Equal(e.Parent, root); Assert.Equal(e.AttachmentPoint, root);
Assert.Equal(e.Root, root); Assert.Equal(e.RootVisual, root);
called2 = true; 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) private static void Layout(Carousel target)
{ {
((ILayoutRoot)target.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass(); target.GetLayoutManager()?.ExecuteLayoutPass();
} }
private static IControlTemplate CarouselTemplate() private static IControlTemplate CarouselTemplate()

2
tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs

@ -110,7 +110,7 @@ namespace Avalonia.Controls.UnitTests
new RawKeyEventArgs( new RawKeyEventArgs(
keyboardDevice, keyboardDevice,
0, 0,
root, root.InputRoot,
RawKeyEventType.KeyDown, RawKeyEventType.KeyDown,
Key.F, Key.F,
RawInputModifiers.Control, RawInputModifiers.Control,

2
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -1218,7 +1218,7 @@ namespace Avalonia.Controls.UnitTests
private static void Layout(Control c) private static void Layout(Control c)
{ {
(c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); c.GetLayoutManager()?.ExecuteLayoutPass();
} }
private static ContentPresenter GetContainer(ItemsControl target, int index = 0) 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) private static void Layout(Control c)
{ {
((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass(); c.GetLayoutManager()?.ExecuteLayoutPass();
} }
private class Item 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() 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>(); var parentMock = new Mock<Control>();
parentMock.As<IContentPresenterHost>(); parentMock.As<IContentPresenterHost>();
parentMock.As<IRenderRoot>(); parentMock.As<IPresentationSource>();
parentMock.As<ILogicalRoot>(); parentMock.As<ILogicalRoot>();
(target as ISetLogicalParent).SetParent(parentMock.Object); (target as ISetLogicalParent).SetParent(parentMock.Object);
@ -102,7 +102,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
}; };
var parentMock = new Mock<Control>(); var parentMock = new Mock<Control>();
parentMock.As<IRenderRoot>(); parentMock.As<IPresentationSource>();
parentMock.As<ILogicalRoot>(); parentMock.As<ILogicalRoot>();
parentMock.As<ILogical>().SetupGet(l => l.IsAttachedToLogicalTree).Returns(true); parentMock.As<ILogical>().SetupGet(l => l.IsAttachedToLogicalTree).Returns(true);
@ -148,7 +148,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var parentMock = new Mock<Control>(); var parentMock = new Mock<Control>();
parentMock.As<IContentPresenterHost>(); parentMock.As<IContentPresenterHost>();
parentMock.As<IRenderRoot>(); parentMock.As<IPresentationSource>();
parentMock.As<ILogicalRoot>(); parentMock.As<ILogicalRoot>();
(target as ISetLogicalParent).SetParent(parentMock.Object); (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(); 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()); Assert.Same(button, focusManager.GetFocusedElement());
//Ensure focus remains in the popup //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()); Assert.Same(textBox, focusManager.GetFocusedElement());
popup.Close(); popup.Close();
@ -702,9 +711,9 @@ namespace Avalonia.Controls.UnitTests.Primitives
button.Focus(); 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()); Assert.Same(button, focusManager.GetFocusedElement());
border1.Child = null; border1.Child = null;
@ -1170,7 +1179,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var ev = new PointerPressedEventArgs( var ev = new PointerPressedEventArgs(
popupContent, popupContent,
pointer, pointer,
(PopupRoot)popupContent.VisualRoot!, (PopupRoot)TopLevel.GetTopLevel(popupContent)!,
new Point(50 , 50), new Point(50 , 50),
0, 0,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),

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

Loading…
Cancel
Save