ソースを参照

Merge remote-tracking branch 'upstream/master'

Benedikt Stebner 3 年 前
コミット
f6223f0c7e
100 ファイル変更1627 行追加1136 行削除
  1. 25 52
      Avalonia.sln
  2. 3 3
      build/SkiaSharp.props
  3. 0 2
      native/Avalonia.Native/src/OSX/AvnPanelWindow.mm
  4. 2 2
      native/Avalonia.Native/src/OSX/AvnView.mm
  5. 58 38
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  6. 1 1
      native/Avalonia.Native/src/OSX/INSWindowHolder.h
  7. 1 5
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  8. 4 5
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  9. 26 41
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  10. 8 1
      native/Avalonia.Native/src/OSX/WindowImpl.h
  11. 79 36
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  12. 0 1
      native/Avalonia.Native/src/OSX/WindowProtocol.h
  13. 11 0
      native/Avalonia.Native/src/OSX/app.mm
  14. 12 3
      readme.md
  15. 35 0
      samples/ControlCatalog.Android/EmbedSample.Android.cs
  16. 5 1
      samples/ControlCatalog.Android/MainActivity.cs
  17. 14 0
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  18. 35 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/EmbedSample.Gtk.cs
  19. 58 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/GtkHelper.cs
  20. 0 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes-license.md
  21. 0 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes.mp4
  22. 29 0
      samples/ControlCatalog.NetCore/NativeControls/Mac/EmbedSample.Mac.cs
  23. 38 0
      samples/ControlCatalog.NetCore/NativeControls/Mac/MacHelper.cs
  24. 45 0
      samples/ControlCatalog.NetCore/NativeControls/Win/EmbedSample.Win.cs
  25. 73 0
      samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
  26. 7 1
      samples/ControlCatalog.NetCore/Program.cs
  27. 28 0
      samples/ControlCatalog.NetCore/app.manifest
  28. 4 0
      samples/ControlCatalog.Web/App.razor.cs
  29. 34 0
      samples/ControlCatalog.Web/EmbedSample.Browser.cs
  30. 0 70
      samples/ControlCatalog.Web/Shared/MainLayout.razor.css
  31. 5 39
      samples/ControlCatalog.Web/wwwroot/css/app.css
  32. 10 1
      samples/ControlCatalog.Web/wwwroot/js/app.js
  33. 8 1
      samples/ControlCatalog.iOS/AppDelegate.cs
  34. 38 0
      samples/ControlCatalog.iOS/EmbedSample.iOS.cs
  35. 15 0
      samples/ControlCatalog/ControlCatalog.csproj
  36. 7 3
      samples/ControlCatalog/Converter/MathSubtractConverter.cs
  37. 5 5
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  38. 5 2
      samples/ControlCatalog/MainView.xaml
  39. 8 8
      samples/ControlCatalog/MainView.xaml.cs
  40. 3 5
      samples/ControlCatalog/MainWindow.xaml.cs
  41. 2 2
      samples/ControlCatalog/Models/Countries.cs
  42. 2 2
      samples/ControlCatalog/Models/GDPValueConverter.cs
  43. 8 8
      samples/ControlCatalog/Models/Person.cs
  44. 19 14
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  45. 1 1
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  46. 2 2
      samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
  47. 6 6
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs
  48. 2 2
      samples/ControlCatalog/Pages/CalendarPage.xaml.cs
  49. 6 5
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  50. 11 11
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs
  51. 3 3
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  52. 4 4
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  53. 2 2
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  54. 1 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  55. 42 20
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  56. 3 3
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  57. 4 4
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  58. 8 5
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  59. 15 11
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  60. 1 1
      samples/ControlCatalog/Pages/LabelsPage.axaml.cs
  61. 1 1
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  62. 68 0
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml
  63. 86 0
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs
  64. 1 1
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  65. 5 5
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  66. 1 1
      samples/ControlCatalog/Pages/ScreenPage.cs
  67. 6 6
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  68. 1 1
      samples/ControlCatalog/Pages/TabStripPage.xaml.cs
  69. 1 1
      samples/ControlCatalog/ViewModels/ApplicationViewModel.cs
  70. 1 1
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  71. 1 1
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  72. 10 9
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  73. 7 6
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  74. 4 4
      samples/ControlCatalog/ViewModels/MenuItemViewModel.cs
  75. 1 1
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  76. 2 2
      samples/ControlCatalog/ViewModels/NotificationViewModel.cs
  77. 8 8
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  78. 2 2
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  79. 0 8
      samples/interop/NativeEmbedSample/App.xaml
  80. 0 22
      samples/interop/NativeEmbedSample/App.xaml.cs
  81. 0 121
      samples/interop/NativeEmbedSample/EmbedSample.cs
  82. 0 58
      samples/interop/NativeEmbedSample/GtkHelper.cs
  83. 0 39
      samples/interop/NativeEmbedSample/MacHelper.cs
  84. 0 52
      samples/interop/NativeEmbedSample/MainWindow.xaml
  85. 0 36
      samples/interop/NativeEmbedSample/MainWindow.xaml.cs
  86. 0 31
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  87. 0 17
      samples/interop/NativeEmbedSample/Program.cs
  88. 0 74
      samples/interop/NativeEmbedSample/WinApi.cs
  89. 32 0
      src/Android/Avalonia.Android/AndroidViewControlHandle.cs
  90. 5 4
      src/Android/Avalonia.Android/AvaloniaView.cs
  91. 139 0
      src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs
  92. 7 3
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  93. 1 1
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  94. 8 1
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  95. 38 38
      src/Avalonia.Base/Matrix.cs
  96. 2 1
      src/Avalonia.Base/Media/GeometryDrawing.cs
  97. 9 2
      src/Avalonia.Base/Media/TextFormatting/TextBounds.cs
  98. 19 10
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  99. 241 135
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  100. 39 0
      src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs

+ 25 - 52
Avalonia.sln

@@ -97,6 +97,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\DevAnalyzers.props = build\DevAnalyzers.props
 		build\EmbedXaml.props = build\EmbedXaml.props
 		build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
+		build\ImageSharp.props = build\ImageSharp.props
 		build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
 		build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
 		build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
@@ -117,7 +118,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\System.Memory.props = build\System.Memory.props
 		build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
 		build\XUnit.props = build\XUnit.props
-		build\ImageSharp.props = build\ImageSharp.props
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
@@ -179,8 +179,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
@@ -1413,6 +1411,30 @@ Global
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.Build.0 = Release|Any CPU
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.Build.0 = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@@ -1509,30 +1531,6 @@ Global
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.Build.0 = Release|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.ActiveCfg = Release|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.Build.0 = Release|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@@ -1965,30 +1963,6 @@ Global
 		{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhone.Build.0 = Release|Any CPU
 		{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.Build.0 = Release|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.ActiveCfg = Release|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.Build.0 = Release|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
-		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2035,7 +2009,6 @@ Global
 		{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
-		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 		{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
 		{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}

+ 3 - 3
build/SkiaSharp.props

@@ -1,7 +1,7 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="SkiaSharp" Version="2.88.0" />
-    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0" />
-    <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0" />
+    <PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
+    <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1-preview.1" />
   </ItemGroup>
 </Project>

+ 0 - 2
native/Avalonia.Native/src/OSX/AvnPanelWindow.mm

@@ -3,8 +3,6 @@
 // Copyright (c) 2022 Avalonia. All rights reserved.
 //
 
-#pragma once
-
 #define IS_NSPANEL
 
 #include "AvnWindow.mm"

+ 2 - 2
native/Avalonia.Native/src/OSX/AvnView.mm

@@ -222,7 +222,7 @@
 
 - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
 {
-    bool triggerInputWhenDisabled = type != Move;
+    bool triggerInputWhenDisabled = type != Move && type != LeaveWindow;
 
     if([self ignoreUserInput: triggerInputWhenDisabled])
     {
@@ -709,4 +709,4 @@
     return [[self accessibilityChild] accessibilityFocusedUIElement];
 }
 
-@end
+@end

+ 58 - 38
native/Avalonia.Native/src/OSX/AvnWindow.mm

@@ -33,6 +33,7 @@
     bool _isEnabled;
     bool _canBecomeKeyWindow;
     bool _isExtended;
+    bool _isTransitioningToFullScreen;
     AvnMenu* _menu;
 }
 
@@ -68,7 +69,7 @@
     }
 }
 
-- (void)performClose:(id)sender
+- (void)performClose:(id _Nullable )sender
 {
     if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
     {
@@ -147,7 +148,7 @@
     }
 }
 
--(void) applyMenu:(AvnMenu *)menu
+-(void) applyMenu:(AvnMenu *_Nullable)menu
 {
     if(menu == nullptr)
     {
@@ -157,7 +158,7 @@
     _menu = menu;
 }
 
--(CLASS_NAME*)  initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
+-(CLASS_NAME*_Nonnull)  initWithParent: (WindowBaseImpl*_Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
 {
     // https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/
     // create nswindow with specific contentRect, otherwise we wont be able to resize the window
@@ -175,15 +176,17 @@
     [self setBackgroundColor: [NSColor clearColor]];
 
     _isExtended = false;
+    _isTransitioningToFullScreen = false;
 
-#ifdef IS_NSPANEL
-    [self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary];
-#endif
+    if(self.isDialog)
+    {
+        [self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary];
+    }
 
     return self;
 }
 
-- (BOOL)windowShouldClose:(NSWindow *)sender
+- (BOOL)windowShouldClose:(NSWindow *_Nonnull)sender
 {
     auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
 
@@ -195,21 +198,28 @@
     return true;
 }
 
-- (void)windowDidChangeBackingProperties:(NSNotification *)notification
+- (void)windowDidChangeBackingProperties:(NSNotification *_Nonnull)notification
 {
     [self backingScaleFactor];
 }
 
 
 
-- (void)windowWillClose:(NSNotification *)notification
+- (void)windowWillClose:(NSNotification *_Nonnull)notification
 {
     _closed = true;
     if(_parent)
     {
         ComPtr<WindowBaseImpl> parent = _parent;
         _parent = NULL;
-        [self restoreParentWindow];
+        
+        auto window = dynamic_cast<WindowImpl*>(parent.getRaw());
+        
+        if(window != nullptr)
+        {
+            window->SetParent(nullptr);
+        }
+        
         parent->BaseEvents->Closed();
         [parent->View onClosed];
     }
@@ -220,17 +230,11 @@
     if(_canBecomeKeyWindow)
     {
         // If the window has a child window being shown as a dialog then don't allow it to become the key window.
-        for(NSWindow* uch in [self childWindows])
+        auto parent = dynamic_cast<WindowImpl*>(_parent.getRaw());
+        
+        if(parent != nullptr)
         {
-            if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)])
-            {
-                continue;
-            }
-
-            id <AvnWindowProtocol> ch = (id <AvnWindowProtocol>) uch;
-
-            if(ch.isDialog)
-                return false;
+            return parent->CanBecomeKeyWindow();
         }
 
         return true;
@@ -259,6 +263,10 @@
 -(void) setEnabled:(bool)enable
 {
     _isEnabled = enable;
+    
+    [[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
+    [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
+    [[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
 }
 
 -(void)becomeKeyWindow
@@ -273,17 +281,20 @@
     [super becomeKeyWindow];
 }
 
--(void) restoreParentWindow;
+- (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
 {
-    auto parent = [self parentWindow];
-
-    if(parent != nil)
-    {
-        [parent removeChildWindow:self];
-    }
+    _parent->BringToFront();
+    
+    dispatch_async(dispatch_get_main_queue(), ^{
+        @try {
+        [self invalidateShadow];
+        }
+        @finally{
+        }
+    });
 }
 
-- (void)windowDidMiniaturize:(NSNotification *)notification
+- (void)windowDidMiniaturize:(NSNotification *_Nonnull)notification
 {
     auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
 
@@ -293,7 +304,7 @@
     }
 }
 
-- (void)windowDidDeminiaturize:(NSNotification *)notification
+- (void)windowDidDeminiaturize:(NSNotification *_Nonnull)notification
 {
     auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
 
@@ -303,7 +314,7 @@
     }
 }
 
-- (void)windowDidResize:(NSNotification *)notification
+- (void)windowDidResize:(NSNotification *_Nonnull)notification
 {
     auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
 
@@ -313,7 +324,7 @@
     }
 }
 
-- (void)windowWillExitFullScreen:(NSNotification *)notification
+- (void)windowWillExitFullScreen:(NSNotification *_Nonnull)notification
 {
     auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
 
@@ -323,7 +334,7 @@
     }
 }
 
-- (void)windowDidExitFullScreen:(NSNotification *)notification
+- (void)windowDidExitFullScreen:(NSNotification *_Nonnull)notification
 {
     auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
 
@@ -346,8 +357,9 @@
     }
 }
 
-- (void)windowWillEnterFullScreen:(NSNotification *)notification
+- (void)windowWillEnterFullScreen:(NSNotification *_Nonnull)notification
 {
+    _isTransitioningToFullScreen = true;
     auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
 
     if(parent != nullptr)
@@ -356,8 +368,9 @@
     }
 }
 
-- (void)windowDidEnterFullScreen:(NSNotification *)notification
+- (void)windowDidEnterFullScreen:(NSNotification *_Nonnull)notification
 {
+    _isTransitioningToFullScreen = false;
     auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
 
     if(parent != nullptr)
@@ -367,7 +380,7 @@
     }
 }
 
-- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
+- (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame
 {
     return true;
 }
@@ -378,11 +391,13 @@
         _parent->BaseEvents->Deactivated();
 
     [self showAppMenuOnly];
+    
+    [self invalidateShadow];
 
     [super resignKeyWindow];
 }
 
-- (void)windowDidMove:(NSNotification *)notification
+- (void)windowDidMove:(NSNotification *_Nonnull)notification
 {
     AvnPoint position;
 
@@ -414,7 +429,7 @@
     return pt;
 }
 
-- (void)sendEvent:(NSEvent *)event
+- (void)sendEvent:(NSEvent *_Nonnull)event
 {
     [super sendEvent:event];
 
@@ -437,8 +452,13 @@
 
                     _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
                 }
+                
+                if(!_isTransitioningToFullScreen)
+                {
+                    _parent->BringToFront();
+                }
             }
-                break;
+            break;
 
             case NSEventTypeMouseEntered:
             {

+ 1 - 1
native/Avalonia.Native/src/OSX/INSWindowHolder.h

@@ -11,7 +11,7 @@
 struct INSWindowHolder
 {
     virtual NSWindow* _Nonnull GetNSWindow () = 0;
-    virtual NSView* _Nonnull GetNSView () = 0;
+    virtual AvnView* _Nonnull GetNSView () = 0;
 };
 
 #endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H

+ 1 - 5
native/Avalonia.Native/src/OSX/PopupImpl.mm

@@ -26,17 +26,13 @@ private:
     PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
     {
         WindowEvents = events;
+        [Window setLevel:NSPopUpMenuWindowLevel];
     }
 protected:
     virtual NSWindowStyleMask GetStyle() override
     {
         return NSWindowStyleMaskBorderless;
     }
-    
-    virtual void OnInitialiseNSWindow () override
-    {
-        [Window setLevel:NSPopUpMenuWindowLevel];
-    }
 
 public:
     virtual bool ShouldTakeFocusOnShow() override

+ 4 - 5
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@@ -26,7 +26,7 @@ BEGIN_INTERFACE_MAP()
 
     virtual ~WindowBaseImpl();
 
-    WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl);
+    WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false);
 
     virtual HRESULT ObtainNSWindowHandle(void **ret) override;
 
@@ -38,7 +38,7 @@ BEGIN_INTERFACE_MAP()
 
     virtual NSWindow *GetNSWindow() override;
 
-    virtual NSView *GetNSView() override;
+    virtual AvnView *GetNSView() override;
 
     virtual HRESULT Show(bool activate, bool isDialog) override;
 
@@ -99,18 +99,17 @@ BEGIN_INTERFACE_MAP()
     virtual bool IsDialog();
 
     id<AvnWindowProtocol> GetWindowProtocol ();
+                           
+    virtual void BringToFront ();
 
 protected:
     virtual NSWindowStyleMask GetStyle();
 
     void UpdateStyle();
-                           
-    virtual void OnInitialiseNSWindow ();
 
 private:
     void CreateNSWindow (bool isDialog);
     void CleanNSWindow ();
-    void InitialiseNSWindow ();
 
     NSCursor *cursor;
     ComPtr<IAvnGlContext> _glContext;

+ 26 - 41
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@@ -21,7 +21,7 @@ WindowBaseImpl::~WindowBaseImpl() {
     Window = nullptr;
 }
 
-WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) {
+WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) {
     _shown = false;
     _inResize = false;
     BaseEvents = events;
@@ -36,8 +36,19 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl)
     lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
     lastMinSize = NSSize { 0, 0 };
 
-    Window = nullptr;
     lastMenu = nullptr;
+    
+    CreateNSWindow(usePanel);
+    
+    [Window setContentView:StandardContainer];
+    [Window setStyleMask:NSWindowStyleMaskBorderless];
+    [Window setBackingType:NSBackingStoreBuffered];
+
+    [Window setContentMinSize:lastMinSize];
+    [Window setContentMaxSize:lastMaxSize];
+
+    [Window setOpaque:false];
+    [Window setHasShadow:true];
 }
 
 HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
@@ -68,7 +79,7 @@ NSWindow *WindowBaseImpl::GetNSWindow() {
     return Window;
 }
 
-NSView *WindowBaseImpl::GetNSView() {
+AvnView *WindowBaseImpl::GetNSView() {
     return View;
 }
 
@@ -88,9 +99,8 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
     START_COM_CALL;
 
     @autoreleasepool {
-        CreateNSWindow(isDialog);
-        InitialiseNSWindow();
-
+        [Window setContentSize:lastSize];
+        
         if(hasPosition)
         {
             SetPosition(lastPositionSet);
@@ -100,6 +110,8 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
         }
 
         UpdateStyle();
+        
+        [Window invalidateShadow];
 
         if (ShouldTakeFocusOnShow() && activate) {
             [Window orderFront:Window];
@@ -143,8 +155,6 @@ HRESULT WindowBaseImpl::Hide() {
     @autoreleasepool {
         if (Window != nullptr) {
             [Window orderOut:Window];
-
-            [GetWindowProtocol() restoreParentWindow];
         }
 
         return S_OK;
@@ -293,8 +303,7 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
             if (!_shown) {
                 BaseEvents->Resized(AvnSize{x, y}, reason);
             }
-
-            if(Window != nullptr) {
+            else if(Window != nullptr) {
                 [Window setContentSize:lastSize];
                 [Window invalidateShadow];
             }
@@ -558,6 +567,8 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
             CleanNSWindow();
 
             Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
+            
+            [Window setHidesOnDeactivate:false];
         }
     } else {
         if (![Window isKindOfClass:[AvnWindow class]]) {
@@ -568,37 +579,6 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
     }
 }
 
-void WindowBaseImpl::OnInitialiseNSWindow()
-{
-    
-}
-
-void WindowBaseImpl::InitialiseNSWindow() {
-    if(Window != nullptr) {
-        [Window setContentView:StandardContainer];
-        [Window setStyleMask:NSWindowStyleMaskBorderless];
-        [Window setBackingType:NSBackingStoreBuffered];
-
-        [Window setContentSize:lastSize];
-        [Window setContentMinSize:lastMinSize];
-        [Window setContentMaxSize:lastMaxSize];
-
-        [Window setOpaque:false];
-        
-        [Window invalidateShadow];
-
-        if (lastMenu != nullptr) {
-            [GetWindowProtocol() applyMenu:lastMenu];
-
-            if ([Window isKeyWindow]) {
-                [GetWindowProtocol() showWindowMenuWithAppMenu];
-            }
-        }
-        
-        OnInitialiseNSWindow();
-    }
-}
-
 id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
     if(Window == nullptr)
     {
@@ -608,6 +588,11 @@ id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
     return (id <AvnWindowProtocol>) Window;
 }
 
+void WindowBaseImpl::BringToFront()
+{
+    // do nothing.
+}
+
 extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
 {
     @autoreleasepool

+ 8 - 1
native/Avalonia.Native/src/OSX/WindowImpl.h

@@ -8,10 +8,12 @@
 
 #import "WindowBaseImpl.h"
 #include "IWindowStateChanged.h"
+#include <list>
 
 class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
 {
 private:
+    bool _isEnabled;
     bool _canResize;
     bool _fullScreenActive;
     SystemDecorations _decorations;
@@ -22,6 +24,8 @@ private:
     bool _transitioningWindowState;
     bool _isClientAreaExtended;
     bool _isDialog;
+    WindowImpl* _parent;
+    std::list<WindowImpl*> _children;
     AvnExtendClientAreaChromeHints _extendClientHints;
 
     FORWARD_IUNKNOWN()
@@ -89,12 +93,15 @@ BEGIN_INTERFACE_MAP()
 
     virtual bool IsDialog() override;
     
-    virtual void OnInitialiseNSWindow() override;
+    virtual void BringToFront () override;
+    
+    bool CanBecomeKeyWindow ();
 
 protected:
     virtual NSWindowStyleMask GetStyle() override;
 
 private:
+    void OnInitialiseNSWindow();
     NSString *_lastTitle;
 };
 

+ 79 - 36
native/Avalonia.Native/src/OSX/WindowImpl.mm

@@ -10,6 +10,8 @@
 #include "WindowProtocol.h"
 
 WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
+    _isEnabled = true;
+    _children = std::list<WindowImpl*>();
     _isClientAreaExtended = false;
     _extendClientHints = AvnDefaultChrome;
     _fullScreenActive = false;
@@ -20,7 +22,10 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
     _lastWindowState = Normal;
     _actualWindowState = Normal;
     _lastTitle = @"";
+    _parent = nullptr;
     WindowEvents = events;
+    
+    OnInitialiseNSWindow();
 }
 
 void WindowImpl::HideOrShowTrafficLights() {
@@ -28,28 +33,17 @@ void WindowImpl::HideOrShowTrafficLights() {
         return;
     }
 
-    for (id subview in Window.contentView.superview.subviews) {
-        if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
-            NSView *titlebarView = [subview subviews][0];
-            for (id button in titlebarView.subviews) {
-                if ([button isKindOfClass:[NSButton class]]) {
-                    if (_isClientAreaExtended) {
-                        auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
-
-                        [button setHidden:!wantsChrome];
-                    } else {
-                        [button setHidden:(_decorations != SystemDecorationsFull)];
-                    }
-
-                    [button setWantsLayer:true];
-                }
-            }
-        }
-    }
+    bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+    bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
+    
+    [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
+    [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
+    [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
 }
 
 void WindowImpl::OnInitialiseNSWindow(){
     [GetWindowProtocol() setCanBecomeKeyWindow:true];
+    
     [Window disableCursorRects];
     [Window setTabbingMode:NSWindowTabbingModeDisallowed];
     [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
@@ -81,7 +75,9 @@ HRESULT WindowImpl::SetEnabled(bool enable) {
     START_COM_CALL;
 
     @autoreleasepool {
+        _isEnabled = enable;
         [GetWindowProtocol() setEnabled:enable];
+        UpdateStyle();
         return S_OK;
     }
 }
@@ -90,26 +86,68 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
     START_COM_CALL;
 
     @autoreleasepool {
-        if (parent == nullptr)
-            return E_POINTER;
+        if(_parent != nullptr)
+        {
+            _parent->_children.remove(this);
+            
+            _parent->BringToFront();
+        }
 
         auto cparent = dynamic_cast<WindowImpl *>(parent);
-        if (cparent == nullptr)
-            return E_INVALIDARG;
-
-        // If one tries to show a child window with a minimized parent window, then the parent window will be
-        // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
-        // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
-        if (cparent->WindowState() == Minimized)
-            cparent->SetWindowState(Normal);
+        
+        _parent = cparent;
+        
+        if(_parent != nullptr && Window != nullptr){
+            // If one tries to show a child window with a minimized parent window, then the parent window will be
+            // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
+            // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
+            if (cparent->WindowState() == Minimized)
+                cparent->SetWindowState(Normal);
+
+            [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
+                
+            cparent->_children.push_back(this);
+                
+            UpdateStyle();
+        }
 
-        [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
-        [cparent->Window addChildWindow:Window ordered:NSWindowAbove];
+        return S_OK;
+    }
+}
 
-        UpdateStyle();
+void WindowImpl::BringToFront()
+{
+    if(Window != nullptr)
+    {
+        if(IsDialog())
+        {
+            Activate();
+        }
+        else
+        {
+            [Window orderFront:nullptr];
+        }
+        
+        [Window invalidateShadow];
+        
+        for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
+        {
+            (*iterator)->BringToFront();
+        }
+    }
+}
 
-        return S_OK;
+bool WindowImpl::CanBecomeKeyWindow()
+{
+    for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
+    {
+        if((*iterator)->IsDialog())
+        {
+            return false;
+        }
     }
+    
+    return true;
 }
 
 void WindowImpl::StartStateTransition() {
@@ -523,7 +561,12 @@ bool WindowImpl::IsDialog() {
 }
 
 NSWindowStyleMask WindowImpl::GetStyle() {
-    unsigned long s = this->_isDialog ? NSWindowStyleMaskDocModalWindow : NSWindowStyleMaskBorderless;
+    unsigned long s = NSWindowStyleMaskBorderless;
+    
+    if(_actualWindowState == FullScreen)
+    {
+        s |= NSWindowStyleMaskFullScreen;
+    }
 
     switch (_decorations) {
         case SystemDecorationsNone:
@@ -535,15 +578,15 @@ NSWindowStyleMask WindowImpl::GetStyle() {
             break;
 
         case SystemDecorationsFull:
-            s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless;
+            s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
 
-            if (_canResize) {
+            if (_canResize && _isEnabled) {
                 s = s | NSWindowStyleMaskResizable;
             }
             break;
     }
 
-    if ([Window parentWindow] == nullptr) {
+    if (!IsDialog()) {
         s |= NSWindowStyleMaskMiniaturizable;
     }
 

+ 0 - 1
native/Avalonia.Native/src/OSX/WindowProtocol.h

@@ -11,7 +11,6 @@
 
 @protocol AvnWindowProtocol
 -(void) pollModalSession: (NSModalSession _Nonnull) session;
--(void) restoreParentWindow;
 -(bool) shouldTryToHandleEvents;
 -(void) setEnabled: (bool) enable;
 -(void) showAppMenuOnly;

+ 11 - 0
native/Avalonia.Native/src/OSX/app.mm

@@ -82,6 +82,17 @@ ComPtr<IAvnApplicationEvents> _events;
         _isHandlingSendEvent = oldHandling;
     }
 }
+
+// This is needed for certain embedded controls DO NOT REMOVE..
+- (BOOL) isHandlingSendEvent
+{
+    return _isHandlingSendEvent;
+}
+
+- (void)setHandlingSendEvent:(BOOL)handlingSendEvent
+{
+    _isHandlingSendEvent = handlingSendEvent;
+}
 @end
 
 extern void InitializeAvnApp(IAvnApplicationEvents* events)

+ 12 - 3
readme.md

@@ -70,11 +70,15 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou
 
 Avalonia is licenced under the [MIT licence](licence.md).
 
-## Support Avalonia
+## Donate
 
-**BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx
+Donating to the project is a fantastic way to thank our valued contributors for their hard work. Your donations are shared among our community and awarded for significant contributions.  
+
+If you need support see Commercial Support section below.
+
+Donate with BTC or use [Open Collective](https://opencollective.com/avalonia).
 
-This will be shared with the community and awarded for significant contributions.
+**BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx
 
 ### Backers
 
@@ -98,6 +102,11 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
 <a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>
 <a href="https://baseheadinc.com/" target="_blank"><img height="50" src="https://baseheadinc.com/wp-content/uploads/2020/09/BH-Logo-for-Site-Header-New.png"></a>
 
+## Commercial Support 
+
+We have a range of [support plans available](https://avaloniaui.net/support.html) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process.
+
+*Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [[email protected]](mailto://[email protected])*
 ## .NET Foundation
 
 This project is supported by the [.NET Foundation](https://dotnetfoundation.org).

+ 35 - 0
samples/ControlCatalog.Android/EmbedSample.Android.cs

@@ -0,0 +1,35 @@
+using System;
+using Avalonia.Platform;
+using Avalonia.Android;
+using ControlCatalog.Pages;
+
+namespace ControlCatalog.Android;
+
+public class EmbedSampleAndroid : INativeDemoControl
+{
+    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
+    {
+        var parentContext = (parent as AndroidViewControlHandle)?.View.Context
+            ?? global::Android.App.Application.Context;
+
+        if (isSecond)
+        {
+            var webView = new global::Android.Webkit.WebView(parentContext);
+            webView.LoadUrl("https://www.android.com/");
+
+            return new AndroidViewControlHandle(webView);
+        }
+        else
+        {
+            var button = new global::Android.Widget.Button(parentContext) { Text = "Hello world" };
+            var clickCount = 0;
+            button.Click += (sender, args) =>
+            {
+                clickCount++;
+                button.Text = $"Click count {clickCount}";
+            };
+
+            return new AndroidViewControlHandle(button);
+        }
+    }
+}

+ 5 - 1
samples/ControlCatalog.Android/MainActivity.cs

@@ -10,7 +10,11 @@ namespace ControlCatalog.Android
     {
         protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
         {
-            return base.CustomizeAppBuilder(builder);
+            return base.CustomizeAppBuilder(builder)
+                .AfterSetup(_ =>
+                {
+                    Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
+                });
         }
     }
 }

+ 14 - 0
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -4,6 +4,7 @@
     <OutputType>WinExe</OutputType>
     <TargetFramework>net6.0</TargetFramework>
     <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'">
@@ -12,6 +13,16 @@
     <NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
   </PropertyGroup>
 
+  <ItemGroup>
+    <Compile Include="..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" Link="NativeControls\Gtk\Gtk.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="NativeControls\Gtk\nodes.mp4">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
@@ -20,6 +31,8 @@
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
     <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
+    <!-- For native controls test -->
+    <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
   </ItemGroup>
 
   <ItemGroup Condition="'$(RunNativeAotCompilation)' == 'true'">
@@ -32,6 +45,7 @@
   <PropertyGroup>
     <!-- For Microsoft.CodeAnalysis -->
     <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
   </PropertyGroup>
 
   <Import Project="..\..\build\SampleApp.props" />

+ 35 - 0
samples/ControlCatalog.NetCore/NativeControls/Gtk/EmbedSample.Gtk.cs

@@ -0,0 +1,35 @@
+using System.IO;
+using System.Diagnostics;
+using Avalonia.Platform;
+using Avalonia.Controls.Platform;
+using System;
+using ControlCatalog.Pages;
+
+namespace ControlCatalog.NetCore;
+
+public class EmbedSampleGtk : INativeDemoControl
+{
+    private Process _mplayer;
+
+    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
+    {
+        if (isSecond)
+        {
+            var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
+            if (chooser != null)
+                return chooser;
+        }
+
+        var control = createDefault();
+        var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
+            "..",
+            "nodes.mp4"));
+        _mplayer = Process.Start(new ProcessStartInfo("mplayer",
+            $"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
+        {
+            UseShellExecute = false,
+
+        });
+        return control;
+    }
+}

+ 58 - 0
samples/ControlCatalog.NetCore/NativeControls/Gtk/GtkHelper.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform.Interop;
+using Avalonia.X11.NativeDialogs;
+using static Avalonia.X11.NativeDialogs.Gtk;
+using static Avalonia.X11.NativeDialogs.Glib;
+
+namespace ControlCatalog.NetCore;
+
+internal class GtkHelper
+{
+    private static Task<bool> s_gtkTask;
+
+    class FileChooser : INativeControlHostDestroyableControlHandle
+    {
+        private readonly IntPtr _widget;
+
+        public FileChooser(IntPtr widget, IntPtr xid)
+        {
+            _widget = widget;
+            Handle = xid;
+        }
+
+        public IntPtr Handle { get; }
+        public string HandleDescriptor => "XID";
+
+        public void Destroy()
+        {
+            RunOnGlibThread(() =>
+            {
+                gtk_widget_destroy(_widget);
+                return 0;
+            }).Wait();
+        }
+    }
+
+
+    public static INativeControlHostDestroyableControlHandle CreateGtkFileChooser(IntPtr parentXid)
+    {
+        if (s_gtkTask == null)
+            s_gtkTask = StartGtk();
+        if (!s_gtkTask.Result)
+            return null;
+        return RunOnGlibThread(() =>
+        {
+            using (var title = new Utf8Buffer("Embedded"))
+            {
+                var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
+                    IntPtr.Zero);
+                gtk_widget_realize(widget);
+                var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
+                gtk_window_present(widget);
+                return new FileChooser(widget, xid);
+            }
+        }).Result;
+    }
+}

+ 0 - 0
samples/interop/NativeEmbedSample/nodes-license.md → samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes-license.md


+ 0 - 0
samples/interop/NativeEmbedSample/nodes.mp4 → samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes.mp4


+ 29 - 0
samples/ControlCatalog.NetCore/NativeControls/Mac/EmbedSample.Mac.cs

@@ -0,0 +1,29 @@
+using System;
+
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+using ControlCatalog.Pages;
+
+using MonoMac.Foundation;
+using MonoMac.WebKit;
+
+namespace ControlCatalog.NetCore;
+
+public class EmbedSampleMac : INativeDemoControl
+{
+    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
+    {
+        // Note: We are using MonoMac for example purposes
+        // It shouldn't be used in production apps
+        MacHelper.EnsureInitialized();
+
+        var webView = new WebView();
+        Dispatcher.UIThread.Post(() =>
+        {
+            webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
+                isSecond ? "https://bing.com" : "https://google.com/")));
+        });
+        return new MacOSViewHandle(webView);
+    }
+}

+ 38 - 0
samples/ControlCatalog.NetCore/NativeControls/Mac/MacHelper.cs

@@ -0,0 +1,38 @@
+using System;
+
+using Avalonia.Controls.Platform;
+using MonoMac.AppKit;
+
+namespace ControlCatalog.NetCore;
+
+internal class MacHelper
+{
+    private static bool _isInitialized;
+
+    public static void EnsureInitialized()
+    {
+        if (_isInitialized)
+            return;
+        _isInitialized = true;
+        NSApplication.Init();
+    }
+}
+
+internal class MacOSViewHandle : INativeControlHostDestroyableControlHandle
+{
+    private NSView _view;
+
+    public MacOSViewHandle(NSView view)
+    {
+        _view = view;
+    }
+
+    public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
+    public string HandleDescriptor => "NSView";
+
+    public void Destroy()
+    {
+        _view.Dispose();
+        _view = null;
+    }
+}

+ 45 - 0
samples/ControlCatalog.NetCore/NativeControls/Win/EmbedSample.Win.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Text;
+
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+
+using ControlCatalog.Pages;
+
+namespace ControlCatalog.NetCore;
+
+public class EmbedSampleWin : INativeDemoControl
+{
+    private const string RichText =
+        @"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
+{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
+{\*\generator Riched20 6.3.9600}\viewkind4\uc1 
+\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0  a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
+}";
+
+    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
+    {
+        WinApi.LoadLibrary("Msftedit.dll");
+        var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
+            @"Rich Edit",
+            0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
+            IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
+        var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
+        var text = RichText.Replace("<PREFIX>", isSecond ? "\\qr " : "");
+        var bytes = Encoding.UTF8.GetBytes(text);
+        WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
+        return new Win32WindowControlHandle(handle, "HWND");
+    }
+}
+
+internal class Win32WindowControlHandle : PlatformHandle, INativeControlHostDestroyableControlHandle
+{
+    public Win32WindowControlHandle(IntPtr handle, string descriptor) : base(handle, descriptor)
+    {
+    }
+
+    public void Destroy()
+    {
+        _ = WinApi.DestroyWindow(Handle);
+    }
+}

+ 73 - 0
samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace ControlCatalog.NetCore;
+
+internal unsafe class WinApi
+{
+    public enum CommonControls : uint
+    {
+        ICC_LISTVIEW_CLASSES = 0x00000001, // listview, header
+        ICC_TREEVIEW_CLASSES = 0x00000002, // treeview, tooltips
+        ICC_BAR_CLASSES = 0x00000004, // toolbar, statusbar, trackbar, tooltips
+        ICC_TAB_CLASSES = 0x00000008, // tab, tooltips
+        ICC_UPDOWN_CLASS = 0x00000010, // updown
+        ICC_PROGRESS_CLASS = 0x00000020, // progress
+        ICC_HOTKEY_CLASS = 0x00000040, // hotkey
+        ICC_ANIMATE_CLASS = 0x00000080, // animate
+        ICC_WIN95_CLASSES = 0x000000FF,
+        ICC_DATE_CLASSES = 0x00000100, // month picker, date picker, time picker, updown
+        ICC_USEREX_CLASSES = 0x00000200, // comboex
+        ICC_COOL_CLASSES = 0x00000400, // rebar (coolbar) control
+        ICC_INTERNET_CLASSES = 0x00000800,
+        ICC_PAGESCROLLER_CLASS = 0x00001000, // page scroller
+        ICC_NATIVEFNTCTL_CLASS = 0x00002000, // native font control
+        ICC_STANDARD_CLASSES = 0x00004000,
+        ICC_LINK_CLASS = 0x00008000
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct INITCOMMONCONTROLSEX
+    {
+        public int dwSize;
+        public uint dwICC;
+    }
+
+    [DllImport("Comctl32.dll")]
+    public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern bool DestroyWindow(IntPtr hwnd);
+
+    [DllImport("kernel32.dll")]
+    public static extern IntPtr LoadLibrary(string lib);
+
+
+    [DllImport("kernel32.dll")]
+    public static extern IntPtr GetModuleHandle(string lpModuleName);
+
+    [DllImport("user32.dll", SetLastError = true)]
+    public static extern IntPtr CreateWindowEx(
+        int dwExStyle,
+        string lpClassName,
+        string lpWindowName,
+        uint dwStyle,
+        int x,
+        int y,
+        int nWidth,
+        int nHeight,
+        IntPtr hWndParent,
+        IntPtr hMenu,
+        IntPtr hInstance,
+        IntPtr lpParam);
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct SETTEXTEX
+    {
+        public uint Flags;
+        public uint Codepage;
+    }
+
+    [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
+    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
+}

+ 7 - 1
samples/ControlCatalog.NetCore/Program.cs

@@ -7,11 +7,12 @@ using System.Threading.Tasks;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Dialogs;
 using Avalonia.Headless;
 using Avalonia.LogicalTree;
 using Avalonia.Threading;
 
+using ControlCatalog.Pages;
+
 namespace ControlCatalog.NetCore
 {
     static class Program
@@ -123,6 +124,11 @@ namespace ControlCatalog.NetCore
                     {
                         StartupScreenIndex = 1,
                     });
+
+                    EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin()
+                        : OperatingSystem.IsMacOS() ? new EmbedSampleMac()
+                        : OperatingSystem.IsLinux() ? new EmbedSampleGtk()
+                        : null;
                 })
                 .LogToTrace();
 

+ 28 - 0
samples/ControlCatalog.NetCore/app.manifest

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="ControlCatalog.app"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on
+           and is designed to work with. Uncomment the appropriate elements
+           and Windows will automatically select the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
+
+      <!-- Windows 7 -->
+      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
+
+      <!-- Windows 8 -->
+      <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
+
+      <!-- Windows 8.1 -->
+      <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+    </application>
+  </compatibility>
+</assembly>

+ 4 - 0
samples/ControlCatalog.Web/App.razor.cs

@@ -7,6 +7,10 @@ public partial class App
     protected override void OnParametersSet()
     {
         WebAppBuilder.Configure<ControlCatalog.App>()
+            .AfterSetup(_ =>
+            {
+                ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
+            })
             .SetupWithSingleViewLifetime();
 
         base.OnParametersSet();

+ 34 - 0
samples/ControlCatalog.Web/EmbedSample.Browser.cs

@@ -0,0 +1,34 @@
+using System;
+
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Web.Blazor;
+
+using ControlCatalog.Pages;
+
+using Microsoft.JSInterop;
+
+namespace ControlCatalog.Web;
+
+public class EmbedSampleWeb : INativeDemoControl
+{
+    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
+    {
+        var runtime = AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>();
+
+        if (isSecond)
+        {
+            var iframe = runtime.Invoke<IJSInProcessObjectReference>("document.createElement", "iframe");
+            iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70");
+
+            return new JSObjectControlHandle(iframe);
+        }
+        else
+        {
+            // window.createAppButton source is defined in "app.js" file.
+            var button = runtime.Invoke<IJSInProcessObjectReference>("window.createAppButton");
+
+            return new JSObjectControlHandle(button);
+        }
+    }
+}

+ 0 - 70
samples/ControlCatalog.Web/Shared/MainLayout.razor.css

@@ -1,70 +0,0 @@
-.page {
-    position: relative;
-    display: flex;
-    flex-direction: column;
-}
-
-.main {
-    flex: 1;
-}
-
-.sidebar {
-    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
-}
-
-.top-row {
-    background-color: #f7f7f7;
-    border-bottom: 1px solid #d6d5d5;
-    justify-content: flex-end;
-    height: 3.5rem;
-    display: flex;
-    align-items: center;
-}
-
-    .top-row ::deep a, .top-row .btn-link {
-        white-space: nowrap;
-        margin-left: 1.5rem;
-    }
-
-    .top-row a:first-child {
-        overflow: hidden;
-        text-overflow: ellipsis;
-    }
-
-@media (max-width: 640.98px) {
-    .top-row:not(.auth) {
-        display: none;
-    }
-
-    .top-row.auth {
-        justify-content: space-between;
-    }
-
-    .top-row a, .top-row .btn-link {
-        margin-left: 0;
-    }
-}
-
-@media (min-width: 641px) {
-    .page {
-        flex-direction: row;
-    }
-
-    .sidebar {
-        width: 250px;
-        height: 100vh;
-        position: sticky;
-        top: 0;
-    }
-
-    .top-row {
-        position: sticky;
-        top: 0;
-        z-index: 1;
-    }
-
-    .main > div {
-        padding-left: 2rem !important;
-        padding-right: 1.5rem !important;
-    }
-}

+ 5 - 39
samples/ControlCatalog.Web/wwwroot/css/app.css

@@ -44,47 +44,13 @@ a, .btn-link {
     z-index: 1000;
 }
 
-    #blazor-error-ui .dismiss {
-        cursor: pointer;
-        position: absolute;
-        right: 0.75rem;
-        top: 0.5rem;
-    }
-
-.canvas-container {
-    opacity:1;
-    background-color:#ccc;
-    position:fixed;
-    width:100%;
-    height:100%;
-    top:0px;
-    left:0px;
-    z-index:500;
-}
-
-canvas
-{
-    opacity:1;
-    background-color:#ccc;
-    position:fixed;
-    width:100%;
-    height:100%;
-    top:0px;
-    left:0px;
-    z-index:500;
+#blazor-error-ui .dismiss {
+    cursor: pointer;
+    position: absolute;
+    right: 0.75rem;
+    top: 0.5rem;
 }
 
 #app, .page {
     height: 100%;
 }
-
-.overlay{
-    opacity:0.0;
-    background-color:#ccc;
-    position:fixed;
-    width:100vw;
-    height:100vh;
-    top:0px;
-    left:0px;
-    z-index:1000;
-}

+ 10 - 1
samples/ControlCatalog.Web/wwwroot/js/app.js

@@ -1 +1,10 @@
-
+window.createAppButton = function () {
+    var button = document.createElement('button');
+    button.innerText = 'Hello world';
+    var clickCount = 0;
+    button.onclick = () => {
+        clickCount++;
+        button.innerText = 'Click count ' + clickCount;
+    };
+    return button;
+}

+ 8 - 1
samples/ControlCatalog.iOS/AppDelegate.cs

@@ -13,6 +13,13 @@ namespace ControlCatalog
     [Register("AppDelegate")]
     public partial class AppDelegate : AvaloniaAppDelegate<App>
     {
-        
+        protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
+        {
+            return base.CustomizeAppBuilder(builder)
+                .AfterSetup(_ =>
+                {
+                    Pages.EmbedSample.Implementation = new EmbedSampleIOS();
+                });
+        }
     }
 }

+ 38 - 0
samples/ControlCatalog.iOS/EmbedSample.iOS.cs

@@ -0,0 +1,38 @@
+using System;
+using Avalonia.Platform;
+using CoreGraphics;
+using Foundation;
+using UIKit;
+using WebKit;
+using Avalonia.iOS;
+using ControlCatalog.Pages;
+
+namespace ControlCatalog;
+
+public class EmbedSampleIOS : INativeDemoControl
+{
+    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
+    {
+        if (isSecond)
+        {
+            var webView = new WKWebView(CGRect.Empty, new WKWebViewConfiguration());
+            webView.LoadRequest(new NSUrlRequest(new NSUrl("https://www.apple.com/")));
+
+            return new UIViewControlHandle(webView);
+        }
+        else
+        {
+            var button = new UIButton();
+            var clickCount = 0;
+            button.SetTitle("Hello world", UIControlState.Normal);
+            button.BackgroundColor = UIColor.Blue;
+            button.AddTarget((_, _) =>
+            {
+                clickCount++;
+                button.SetTitle($"Click count {clickCount}", UIControlState.Normal);
+            }, UIControlEvent.TouchDown);
+
+            return new UIViewControlHandle(button);
+        }
+    }
+}

+ 15 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -14,6 +14,9 @@
     <AvaloniaResource Include="Assets\*" />
     <AvaloniaResource Include="Assets\Fonts\*" />
   </ItemGroup>
+  <ItemGroup>
+    <None Remove="Pages\NativeEmbedPage.xaml" />
+  </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
     <EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
@@ -32,5 +35,17 @@
     <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <AvaloniaResource Update="Pages\NativeEmbedPage.xaml">
+      <Generator>MSBuild:Compile</Generator>
+    </AvaloniaResource>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Update="Pages\NativeEmbedPage.xaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+    </Compile>
+  </ItemGroup>
+
   <Import Project="..\..\build\BuildTargets.targets" />
 </Project>

+ 7 - 3
samples/ControlCatalog/Converter/MathSubtractConverter.cs

@@ -6,12 +6,16 @@ namespace ControlCatalog.Converter;
 
 public class MathSubtractConverter : IValueConverter
 {
-    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
     {
-        return (double)value - (double)parameter;
+        if (value is double dv && parameter is double dp)
+        {
+            return dv - dp;
+        }
+        return double.NaN;
     }
 
-    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
     {
         throw new NotSupportedException();
     }

+ 5 - 5
samples/ControlCatalog/DecoratedWindow.xaml.cs

@@ -15,7 +15,7 @@ namespace ControlCatalog
 
         void SetupSide(string name, StandardCursorType cursor, WindowEdge edge)
         {
-            var ctl = this.FindControl<Control>(name);
+            var ctl = this.Get<Control>(name);
             ctl.Cursor = new Cursor(cursor);
             ctl.PointerPressed += (i, e) =>
             {
@@ -26,7 +26,7 @@ namespace ControlCatalog
         private void InitializeComponent()
         {
             AvaloniaXamlLoader.Load(this);
-            this.FindControl<Control>("TitleBar").PointerPressed += (i, e) =>
+            this.Get<Control>("TitleBar").PointerPressed += (i, e) =>
             {
                 PlatformImpl?.BeginMoveDrag(e);
             };
@@ -38,12 +38,12 @@ namespace ControlCatalog
             SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast);
             SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest);
             SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast);
-            this.FindControl<Button>("MinimizeButton").Click += delegate { this.WindowState = WindowState.Minimized; };
-            this.FindControl<Button>("MaximizeButton").Click += delegate
+            this.Get<Button>("MinimizeButton").Click += delegate { this.WindowState = WindowState.Minimized; };
+            this.Get<Button>("MaximizeButton").Click += delegate
             {
                 WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
             };
-            this.FindControl<Button>("CloseButton").Click += delegate
+            this.Get<Button>("CloseButton").Click += delegate
             {
                 Close();
             };

+ 5 - 2
samples/ControlCatalog/MainView.xaml

@@ -2,8 +2,8 @@
              xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples"
-             xmlns:pages="clr-namespace:ControlCatalog.Pages"
-             xmlns:models="clr-namespace:ControlCatalog.Models">
+             xmlns:models="clr-namespace:ControlCatalog.Models"
+             xmlns:pages="clr-namespace:ControlCatalog.Pages">
   <Grid>
     <Grid.Styles>
       <Style Selector="TextBlock.h2">
@@ -157,6 +157,9 @@
       <TabItem Header="Viewbox">
         <pages:ViewboxPage />
       </TabItem>
+      <TabItem Header="Native Embed">
+        <pages:NativeEmbedPage />
+      </TabItem>
       <TabItem Header="Window Customizations">
         <pages:WindowCustomizationsPage />
       </TabItem>

+ 8 - 8
samples/ControlCatalog/MainView.xaml.cs

@@ -18,9 +18,9 @@ namespace ControlCatalog
         {
             AvaloniaXamlLoader.Load(this);
 
-            var sideBar = this.FindControl<TabControl>("Sidebar");
+            var sideBar = this.Get<TabControl>("Sidebar");
 
-            if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().IsDesktop)
+            if (AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo().IsDesktop == true)
             {
                 IList tabItems = ((IList)sideBar.Items);
                 tabItems.Add(new TabItem()
@@ -36,7 +36,7 @@ namespace ControlCatalog
 
             }
 
-            var themes = this.Find<ComboBox>("Themes");
+            var themes = this.Get<ComboBox>("Themes");
             themes.SelectionChanged += (sender, e) =>
             {
                 if (themes.SelectedItem is CatalogTheme theme)
@@ -80,7 +80,7 @@ namespace ControlCatalog
                 }
             };
 
-            var flowDirections = this.Find<ComboBox>("FlowDirection");
+            var flowDirections = this.Get<ComboBox>("FlowDirection");
             flowDirections.SelectionChanged += (sender, e) =>
             {
                 if (flowDirections.SelectedItem is FlowDirection flowDirection)
@@ -89,7 +89,7 @@ namespace ControlCatalog
                 }
             };
 
-            var decorations = this.Find<ComboBox>("Decorations");
+            var decorations = this.Get<ComboBox>("Decorations");
             decorations.SelectionChanged += (sender, e) =>
             {
                 if (VisualRoot is Window window
@@ -99,8 +99,8 @@ namespace ControlCatalog
                 }
             };
 
-            var transparencyLevels = this.Find<ComboBox>("TransparencyLevels");
-            IDisposable backgroundSetter = null, paneBackgroundSetter = null;
+            var transparencyLevels = this.Get<ComboBox>("TransparencyLevels");
+            IDisposable? backgroundSetter = null, paneBackgroundSetter = null;
             transparencyLevels.SelectionChanged += (sender, e) =>
             {
                 backgroundSetter?.Dispose();
@@ -118,7 +118,7 @@ namespace ControlCatalog
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
             base.OnAttachedToVisualTree(e);
-            var decorations = this.Find<ComboBox>("Decorations");
+            var decorations = this.Get<ComboBox>("Decorations");
             if (VisualRoot is Window window)
                 decorations.SelectedIndex = (int)window.SystemDecorations;
         }

+ 3 - 5
samples/ControlCatalog/MainWindow.xaml.cs

@@ -12,7 +12,7 @@ namespace ControlCatalog
     public class MainWindow : Window
     {
         private WindowNotificationManager _notificationArea;
-        private NativeMenu _recentMenu;
+        private NativeMenu? _recentMenu;
 
         public MainWindow()
         {
@@ -28,9 +28,7 @@ namespace ControlCatalog
             };
 
             DataContext = new MainWindowViewModel(_notificationArea);
-            _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
-
-            ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
+            _recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
         }
 
         public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit";
@@ -41,7 +39,7 @@ namespace ControlCatalog
 
         public void OnOpenClicked(object sender, EventArgs args)
         {
-            _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
+            _recentMenu?.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
         }
 
         public void OnCloseClicked(object sender, EventArgs args)

+ 2 - 2
samples/ControlCatalog/Models/Countries.cs

@@ -237,7 +237,7 @@ namespace ControlCatalog.Models
             yield return new Country("Zimbabwe", "SUB-SAHARAN AFRICA", 12236805, 390580, 31.3, 0, 0, 67.69, 1900, 90.7, 26.8, 28.01, 21.84);
         }
 
-        static IReadOnlyList<Country> _all;
+        static IReadOnlyList<Country>? _all;
 
         public static IReadOnlyList<Country> All
         {
@@ -253,4 +253,4 @@ namespace ControlCatalog.Models
 
         }
     }
-}
+}

+ 2 - 2
samples/ControlCatalog/Models/GDPValueConverter.cs

@@ -14,7 +14,7 @@ namespace ControlCatalog.Models
 {
     public class GDPValueConverter : IValueConverter
     {
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             if (value is int gdp)
             {
@@ -29,7 +29,7 @@ namespace ControlCatalog.Models
             return value;
         }
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             throw new NotImplementedException();
         }

+ 8 - 8
samples/ControlCatalog/Models/Person.cs

@@ -13,8 +13,8 @@ namespace ControlCatalog.Models
 {
     public class Person : INotifyDataErrorInfo, INotifyPropertyChanged
     {
-        string _firstName;
-        string _lastName;
+        string _firstName = string.Empty;
+        string _lastName = string.Empty;
         bool _isBanned;
         private int _age;
 
@@ -76,7 +76,7 @@ namespace ControlCatalog.Models
 
         Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>();
 
-        void SetError(string propertyName, string error)
+        void SetError(string propertyName, string? error)
         {
             if (string.IsNullOrEmpty(error))
             {
@@ -88,11 +88,11 @@ namespace ControlCatalog.Models
                 if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
                 {
                     errorList.Clear();
-                    errorList.Add(error);
+                    errorList.Add(error!);
                 }
                 else
                 {
-                    var errors = new List<string> { error };
+                    var errors = new List<string> { error! };
                     _errorLookup.Add(propertyName, errors);
                 }
 
@@ -102,8 +102,8 @@ namespace ControlCatalog.Models
 
         public bool HasErrors => _errorLookup.Count > 0;
 
-        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
-        public event PropertyChangedEventHandler PropertyChanged;
+        public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
+        public event PropertyChangedEventHandler? PropertyChanged;
 
         void OnErrorsChanged(string propertyName)
         {
@@ -114,7 +114,7 @@ namespace ControlCatalog.Models
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
 
-        public IEnumerable GetErrors(string propertyName)
+        public IEnumerable? GetErrors(string propertyName)
         {
             if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
                 return errorList;

+ 19 - 14
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@@ -126,13 +126,13 @@ namespace ControlCatalog.Pages
             binding.Bindings.Add(new Binding("Name"));
             binding.Bindings.Add(new Binding("Abbreviation"));
 
-            var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox");
+            var multibindingBox = this.Get<AutoCompleteBox>("MultiBindingBox");
             multibindingBox.ValueMemberBinding = binding;
 
-            var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox");
+            var asyncBox = this.Get<AutoCompleteBox>("AsyncBox");
             asyncBox.AsyncPopulator = PopulateAsync;
 
-            var customAutocompleteBox = this.FindControl<AutoCompleteBox>("CustomAutocompleteBox");
+            var customAutocompleteBox = this.Get<AutoCompleteBox>("CustomAutocompleteBox");
             customAutocompleteBox.Items = Sentences.SelectMany(x => x);
             customAutocompleteBox.TextFilter = LastWordContains;
             customAutocompleteBox.TextSelector = AppendWord;
@@ -144,11 +144,12 @@ namespace ControlCatalog.Pages
                     .OfType<AutoCompleteBox>();
         }
 
-        private bool StringContains(string str, string query)
+        private bool StringContains(string str, string? query)
         {
+            if (query == null) return false;
             return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
         }
-        private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken)
+        private async Task<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
         {
             await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
 
@@ -157,14 +158,14 @@ namespace ControlCatalog.Pages
                       .ToList();
         }
 
-        private bool LastWordContains(string searchText, string item)
+        private bool LastWordContains(string? searchText, string? item)
         {
-            var words = searchText.Split(' ');
+            var words = searchText?.Split(' ') ?? Array.Empty<string>();
             var options = Sentences.Select(x => x.First).ToArray();
             for (var i = 0; i < words.Length; ++i)
             {
                 var word = words[i];
-                for (var j = 0; j < options.Length; ++j)
+                for (var j = 0; word is { } && j < options.Length; ++j)
                 {
                     var option = options[j];
                     if (option == null)
@@ -183,14 +184,18 @@ namespace ControlCatalog.Pages
 
             return options.Any(x => x != null && x.Value == item);
         }
-        private string AppendWord(string text, string item)
+        private string AppendWord(string? text, string? item)
         {
-            string[] parts = text.Split(' ');
-            if (parts.Length == 0)
-                return item;
+            if (item is { })
+            {
+                string[] parts = text?.Split(' ') ?? Array.Empty<string>();
+                if (parts.Length == 0)
+                    return item;
 
-            parts[parts.Length - 1] = item;
-            return string.Join(" ", parts);
+                parts[parts.Length - 1] = item;
+                return string.Join(" ", parts);
+            }
+            return string.Empty;
         }
 
         private void InitializeComponent()

+ 1 - 1
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs

@@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
             var spinner = (ButtonSpinner)sender;
             var txtBox = (TextBlock)spinner.Content;
 
-            int value = Array.IndexOf(_mountains, txtBox.Text);
+            int value = Array.IndexOf(_mountains, txtBox?.Text);
             if (e.Direction == SpinDirection.Increase)
                 value++;
             else

+ 2 - 2
samples/ControlCatalog/Pages/ButtonsPage.xaml.cs

@@ -11,7 +11,7 @@ namespace ControlCatalog.Pages
         {
             InitializeComponent();
 
-            this.FindControl<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick;
+            this.Get<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick;
         }
 
         private void InitializeComponent()
@@ -22,7 +22,7 @@ namespace ControlCatalog.Pages
         public void OnRepeatButtonClick(object sender, object args)
         {
             repeatButtonClickCount++;
-            var textBlock = this.FindControl<TextBlock>("RepeatButtonTextBlock");
+            var textBlock = this.Get<TextBlock>("RepeatButtonTextBlock");
             textBlock.Text = $"Repeat Button: {repeatButtonClickCount}";
         }
     }

+ 6 - 6
samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs

@@ -10,11 +10,11 @@ namespace ControlCatalog.Pages
         {
             InitializeComponent();
             
-            var dp1 = this.FindControl<CalendarDatePicker>("DatePicker1");
-            var dp2 = this.FindControl<CalendarDatePicker>("DatePicker2");
-            var dp3 = this.FindControl<CalendarDatePicker>("DatePicker3");
-            var dp4 = this.FindControl<CalendarDatePicker>("DatePicker4");
-            var dp5 = this.FindControl<CalendarDatePicker>("DatePicker5");
+            var dp1 = this.Get<CalendarDatePicker>("DatePicker1");
+            var dp2 = this.Get<CalendarDatePicker>("DatePicker2");
+            var dp3 = this.Get<CalendarDatePicker>("DatePicker3");
+            var dp4 = this.Get<CalendarDatePicker>("DatePicker4");
+            var dp5 = this.Get<CalendarDatePicker>("DatePicker5");
 
             dp1.SelectedDate = DateTime.Today;
             dp2.SelectedDate = DateTime.Today.AddDays(10);
@@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
 
             dp4.TemplateApplied += (s, e) =>
             {
-                dp4.BlackoutDates.AddDatesInPast();
+                dp4.BlackoutDates?.AddDatesInPast();
             };
             
         }

+ 2 - 2
samples/ControlCatalog/Pages/CalendarPage.xaml.cs

@@ -11,11 +11,11 @@ namespace ControlCatalog.Pages
             this.InitializeComponent();
 
             var today = DateTime.Today; 
-            var cal1 = this.FindControl<Calendar>("DisplayDatesCalendar");
+            var cal1 = this.Get<Calendar>("DisplayDatesCalendar");
             cal1.DisplayDateStart = today.AddDays(-25);
             cal1.DisplayDateEnd = today.AddDays(25);
 
-            var cal2 = this.FindControl<Calendar>("BlackoutDatesCalendar");
+            var cal2 = this.Get<Calendar>("BlackoutDatesCalendar");
             cal2.BlackoutDates.AddDatesInPast();
             cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6)));
         }

+ 6 - 5
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@@ -16,6 +16,11 @@ namespace ControlCatalog.Pages
         public CarouselPage()
         {
             this.InitializeComponent();
+            _carousel = this.Get<Carousel>("carousel");
+            _left = this.Get<Button>("left");
+            _right = this.Get<Button>("right");
+            _transition = this.Get<ComboBox>("transition");
+            _orientation = this.Get<ComboBox>("orientation");
             _left.Click += (s, e) => _carousel.Previous();
             _right.Click += (s, e) => _carousel.Next();
             _transition.SelectionChanged += TransitionChanged;
@@ -25,11 +30,7 @@ namespace ControlCatalog.Pages
         private void InitializeComponent()
         {
             AvaloniaXamlLoader.Load(this);
-            _carousel = this.FindControl<Carousel>("carousel");
-            _left = this.FindControl<Button>("left");
-            _right = this.FindControl<Button>("right");
-            _transition = this.FindControl<ComboBox>("transition");
-            _orientation = this.FindControl<ComboBox>("orientation");
+
         }
 
         private void TransitionChanged(object sender, SelectionChangedEventArgs e)

+ 11 - 11
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs

@@ -18,29 +18,29 @@ namespace ControlCatalog.Pages
 
             DataContext = new ContextPageViewModel();
 
-            _textBox = this.FindControl<TextBox>("TextBox");
+            _textBox = this.Get<TextBox>("TextBox");
 
-            var cutButton = this.FindControl<Button>("CutButton");
+            var cutButton = this.Get<Button>("CutButton");
             cutButton.Click += CloseFlyout;
 
-            var copyButton = this.FindControl<Button>("CopyButton");
+            var copyButton = this.Get<Button>("CopyButton");
             copyButton.Click += CloseFlyout;
 
-            var pasteButton = this.FindControl<Button>("PasteButton");
+            var pasteButton = this.Get<Button>("PasteButton");
             pasteButton.Click += CloseFlyout;
 
-            var clearButton = this.FindControl<Button>("ClearButton");
+            var clearButton = this.Get<Button>("ClearButton");
             clearButton.Click += CloseFlyout;
 
-            var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder");
+            var customContextRequestedBorder = this.Get<Border>("CustomContextRequestedBorder");
             customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
 
-            var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder");
+            var cancellableContextBorder = this.Get<Border>("CancellableContextBorder");
             cancellableContextBorder.ContextFlyout!.Closing += ContextFlyoutPage_Closing;
             cancellableContextBorder.ContextFlyout!.Opening += ContextFlyoutPage_Opening;
         }
 
-        private ContextPageViewModel _model;
+        private ContextPageViewModel? _model;
         protected override void OnDataContextChanged(EventArgs e)
         {
             if (_model != null)
@@ -55,7 +55,7 @@ namespace ControlCatalog.Pages
         private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e)
         {
             var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
-            e.Cancel = cancelCloseCheckBox.IsChecked ?? false;
+            e.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
         }
 
         private void ContextFlyoutPage_Opening(object sender, EventArgs e)
@@ -63,13 +63,13 @@ namespace ControlCatalog.Pages
             if (e is CancelEventArgs cancelArgs)
             {
                 var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox");
-                cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false;
+                cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
             }
         }
 
         private void CloseFlyout(object sender, RoutedEventArgs e)
         {
-            _textBox.ContextFlyout.Hide();
+            _textBox.ContextFlyout?.Hide();
         }
 
         public void CustomContextRequested(object sender, ContextRequestedEventArgs e)

+ 3 - 3
samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs

@@ -15,15 +15,15 @@ namespace ControlCatalog.Pages
             this.InitializeComponent();
             DataContext = new ContextPageViewModel();
 
-            var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder");
+            var customContextRequestedBorder = this.Get<Border>("CustomContextRequestedBorder");
             customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
 
-            var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder");
+            var cancellableContextBorder = this.Get<Border>("CancellableContextBorder");
             cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing;
             cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening;
         }
 
-        private ContextPageViewModel _model;
+        private ContextPageViewModel? _model;
         protected override void OnDataContextChanged(EventArgs e)
         {
             if (_model != null)

+ 4 - 4
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@@ -21,7 +21,7 @@ namespace ControlCatalog.Pages
             var dataGridSortDescription = DataGridSortDescription.FromPath(nameof(Country.Region), ListSortDirection.Ascending, new ReversedStringComparer());
             var collectionView1 = new DataGridCollectionView(Countries.All);
             collectionView1.SortDescriptions.Add(dataGridSortDescription);
-            var dg1 = this.FindControl<DataGrid>("dataGrid1");
+            var dg1 = this.Get<DataGrid>("dataGrid1");
             dg1.IsReadOnly = true;
             dg1.LoadingRow += Dg1_LoadingRow;
             dg1.Sorting += (s, a) =>
@@ -37,7 +37,7 @@ namespace ControlCatalog.Pages
             };
             dg1.Items = collectionView1;
 
-            var dg2 = this.FindControl<DataGrid>("dataGridGrouping");
+            var dg2 = this.Get<DataGrid>("dataGridGrouping");
             dg2.IsReadOnly = true;
 
             var collectionView2 = new DataGridCollectionView(Countries.All);
@@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
 
             dg2.Items = collectionView2;
 
-            var dg3 = this.FindControl<DataGrid>("dataGridEdit");
+            var dg3 = this.Get<DataGrid>("dataGridEdit");
             dg3.IsReadOnly = false;
 
             var items = new List<Person>
@@ -58,7 +58,7 @@ namespace ControlCatalog.Pages
 
             dg3.Items = collectionView3;
 
-            var addButton = this.FindControl<Button>("btnAdd");
+            var addButton = this.Get<Button>("btnAdd");
             addButton.Click += (a, b) => collectionView3.AddNew();
         }
 

+ 2 - 2
samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs

@@ -9,12 +9,12 @@ namespace ControlCatalog.Pages
         public DateTimePickerPage()
         {
             this.InitializeComponent();
-            this.FindControl<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
+            this.Get<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
                 "for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " +
                 "These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
                 "Order of month, day, and year is dynamically set based on user date settings";
 
-            this.FindControl<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
+            this.Get<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
                 "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
                 "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
                 "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";

+ 1 - 1
samples/ControlCatalog/Pages/DialogsPage.xaml

@@ -20,7 +20,7 @@
                  Text="Window dialogs" />
       <Button Name="DecoratedWindow">Decorated _window</Button>
       <Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button>
-      <Button Name="Dialog">_Dialog</Button>
+      <Button Name="Dialog" ToolTip.Tip="Shows a dialog">_Dialog</Button>
       <Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button>
       <Button Name="OwnedWindow">Own_ed window</Button>
       <Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button>

+ 42 - 20
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@@ -16,14 +16,14 @@ namespace ControlCatalog.Pages
         {
             this.InitializeComponent();
 
-            var results = this.FindControl<ItemsPresenter>("PickerLastResults");
-            var resultsVisible = this.FindControl<TextBlock>("PickerLastResultsVisible");
+            var results = this.Get<ItemsPresenter>("PickerLastResults");
+            var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
 
-            string lastSelectedDirectory = null;
+            string? lastSelectedDirectory = null;
 
             List<FileDialogFilter>? GetFilters()
             {
-                if (this.FindControl<CheckBox>("UseFilters").IsChecked != true)
+                if (this.Get<CheckBox>("UseFilters").IsChecked != true)
                     return null;
                 return  new List<FileDialogFilter>
                 {
@@ -39,20 +39,24 @@ namespace ControlCatalog.Pages
                 };
             }
 
-            this.FindControl<Button>("OpenFile").Click += async delegate
+            this.Get<Button>("OpenFile").Click += async delegate
             {
+                // Almost guaranteed to exist
+                var fullPath = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName;
+                var initialFileName = fullPath == null ? null : System.IO.Path.GetFileName(fullPath);
+                var initialDirectory = fullPath == null ? null : System.IO.Path.GetDirectoryName(fullPath);
+
                 var result = await new OpenFileDialog()
                 {
                     Title = "Open file",
                     Filters = GetFilters(),
-                    Directory = lastSelectedDirectory,
-                    // Almost guaranteed to exist
-                    InitialFileName = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName
+                    Directory = initialDirectory,
+                    InitialFileName = initialFileName
                 }.ShowAsync(GetWindow());
                 results.Items = result;
                 resultsVisible.IsVisible = result?.Any() == true;
             };
-            this.FindControl<Button>("OpenMultipleFiles").Click += async delegate
+            this.Get<Button>("OpenMultipleFiles").Click += async delegate
             {
                 var result = await new OpenFileDialog()
                 {
@@ -64,7 +68,7 @@ namespace ControlCatalog.Pages
                 results.Items = result;
                 resultsVisible.IsVisible = result?.Any() == true;
             };
-            this.FindControl<Button>("SaveFile").Click += async delegate
+            this.Get<Button>("SaveFile").Click += async delegate
             {
                 var result = await new SaveFileDialog()
                 {
@@ -76,18 +80,23 @@ namespace ControlCatalog.Pages
                 results.Items = new[] { result };
                 resultsVisible.IsVisible = result != null;
             };
-            this.FindControl<Button>("SelectFolder").Click += async delegate
+            this.Get<Button>("SelectFolder").Click += async delegate
             {
                 var result = await new OpenFolderDialog()
                 {
                     Title = "Select folder",
                     Directory = lastSelectedDirectory,
                 }.ShowAsync(GetWindow());
-                lastSelectedDirectory = result;
+
+                if (!string.IsNullOrEmpty(result))
+                {
+                    lastSelectedDirectory = result;
+                }
+
                 results.Items = new [] { result };
                 resultsVisible.IsVisible = result != null;
             };
-            this.FindControl<Button>("OpenBoth").Click += async delegate
+            this.Get<Button>("OpenBoth").Click += async delegate
             {
                 var result = await new OpenFileDialog()
                 {
@@ -101,35 +110,35 @@ namespace ControlCatalog.Pages
                 results.Items = result;
                 resultsVisible.IsVisible = result?.Any() == true;
             };
-            this.FindControl<Button>("DecoratedWindow").Click += delegate
+            this.Get<Button>("DecoratedWindow").Click += delegate
             {
                 new DecoratedWindow().Show();
             };
-            this.FindControl<Button>("DecoratedWindowDialog").Click += delegate
+            this.Get<Button>("DecoratedWindowDialog").Click += delegate
             {
                 new DecoratedWindow().ShowDialog(GetWindow());
             };
-            this.FindControl<Button>("Dialog").Click += delegate
+            this.Get<Button>("Dialog").Click += delegate
             {
                 var window = CreateSampleWindow();
                 window.Height = 200;
                 window.ShowDialog(GetWindow());
             };
-            this.FindControl<Button>("DialogNoTaskbar").Click += delegate
+            this.Get<Button>("DialogNoTaskbar").Click += delegate
             {
                 var window = CreateSampleWindow();
                 window.Height = 200;
                 window.ShowInTaskbar = false;
                 window.ShowDialog(GetWindow());
             };
-            this.FindControl<Button>("OwnedWindow").Click += delegate
+            this.Get<Button>("OwnedWindow").Click += delegate
             {
                 var window = CreateSampleWindow();
 
                 window.Show(GetWindow());
             };
 
-            this.FindControl<Button>("OwnedWindowNoTaskbar").Click += delegate
+            this.Get<Button>("OwnedWindowNoTaskbar").Click += delegate
             {
                 var window = CreateSampleWindow();
 
@@ -142,6 +151,7 @@ namespace ControlCatalog.Pages
         private Window CreateSampleWindow()
         {
             Button button;
+            Button dialogButton;
             
             var window = new Window
             {
@@ -158,6 +168,12 @@ namespace ControlCatalog.Pages
                             HorizontalAlignment = HorizontalAlignment.Center,
                             Content = "Click to close",
                             IsDefault = true
+                        }),
+                        (dialogButton = new Button
+                        {
+                            HorizontalAlignment = HorizontalAlignment.Center,
+                            Content = "Dialog",
+                            IsDefault = false
                         })
                     }
                 },
@@ -165,11 +181,17 @@ namespace ControlCatalog.Pages
             };
 
             button.Click += (_, __) => window.Close();
+            dialogButton.Click += (_, __) =>
+            {
+                var dialog = CreateSampleWindow();
+                dialog.Height = 200;
+                dialog.ShowDialog(window);
+            };
 
             return window;
         }
 
-        Window GetWindow() => (Window)this.VisualRoot;
+        Window GetWindow() => this.VisualRoot as Window  ?? throw new NullReferenceException("Invalid Owner");
 
         private void InitializeComponent()
         {

+ 3 - 3
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@@ -14,7 +14,7 @@ namespace ControlCatalog.Pages
         public DragAndDropPage()
         {
             this.InitializeComponent();
-            _DropState = this.Find<TextBlock>("DropState");
+            _DropState = this.Get<TextBlock>("DropState");
 
             int textCount = 0;
             SetupDnd("Text", d => d.Set(DataFormats.Text,
@@ -26,8 +26,8 @@ namespace ControlCatalog.Pages
 
         void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
         {
-            var dragMe = this.Find<Border>("DragMe" + suffix);
-            var dragState = this.Find<TextBlock>("DragState"+suffix);
+            var dragMe = this.Get<Border>("DragMe" + suffix);
+            var dragState = this.Get<TextBlock>("DragState"+suffix);
 
             async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
             {

+ 4 - 4
samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs

@@ -35,7 +35,7 @@ namespace ControlCatalog.Pages
 
         private void SetXamlTexts()
         {
-            var bfxt = this.FindControl<TextBlock>("ButtonFlyoutXamlText");
+            var bfxt = this.Get<TextBlock>("ButtonFlyoutXamlText");
             bfxt.Text = "<Button Content=\"Click me!\">\n" +
                         "    <Button.Flyout>\n" +
                         "        <Flyout>\n" +
@@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
                         "        </Flyout>\n" +
                         "    </Button.Flyout>\n</Button>";
 
-            var mfxt = this.FindControl<TextBlock>("MenuFlyoutXamlText");
+            var mfxt = this.Get<TextBlock>("MenuFlyoutXamlText");
             mfxt.Text = "<Button Content=\"Click me!\">\n" +
                     "    <Button.Flyout>\n" +
                     "        <MenuFlyout>\n" +
@@ -54,7 +54,7 @@ namespace ControlCatalog.Pages
                     "        </MenuFlyout>\n" +
                     "    </Button.Flyout>\n</Button>";
 
-            var afxt = this.FindControl<TextBlock>("AttachedFlyoutXamlText");
+            var afxt = this.Get<TextBlock>("AttachedFlyoutXamlText");
             afxt.Text = "<Panel Name=\"AttachedFlyoutPanel\">\n" +
                 "    <FlyoutBase.AttachedFlyout>\n" +
                 "        <Flyout>\n" +
@@ -66,7 +66,7 @@ namespace ControlCatalog.Pages
                 "\n\n In DoubleTapped handler:\n" +
                 "FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);";
 
-            var sfxt = this.FindControl<TextBlock>("SharedFlyoutXamlText");
+            var sfxt = this.Get<TextBlock>("SharedFlyoutXamlText");
             sfxt.Text = "Declare a flyout in Resources:\n" +
                 "<Window.Resources>\n" +
                 "    <Flyout x:Key=\"SharedFlyout\">\n" +

+ 8 - 5
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@@ -17,9 +17,9 @@ namespace ControlCatalog.Pages
         public ImagePage()
         {
             InitializeComponent();
-            _bitmapImage = this.FindControl<Image>("bitmapImage");
-            _drawingImage = this.FindControl<Image>("drawingImage");
-            _croppedImage = this.FindControl<Image>("croppedImage");
+            _bitmapImage = this.Get<Image>("bitmapImage");
+            _drawingImage = this.Get<Image>("drawingImage");
+            _croppedImage = this.Get<Image>("croppedImage");
         }
 
         private void InitializeComponent()
@@ -50,8 +50,11 @@ namespace ControlCatalog.Pages
             if (_croppedImage != null)
             {
                 var comboxBox = (ComboBox)sender;
-                var croppedBitmap = _croppedImage.Source as CroppedBitmap;
-                croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex);
+                if (_croppedImage.Source is CroppedBitmap croppedBitmap)
+                {
+                    croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex);
+                }
+                
             }
         }
 

+ 15 - 11
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -24,11 +24,11 @@ namespace ControlCatalog.Pages
         public ItemsRepeaterPage()
         {
             this.InitializeComponent();
-            _repeater = this.FindControl<ItemsRepeater>("repeater");
-            _scroller = this.FindControl<ScrollViewer>("scroller");
-            _scrollToLast = this.FindControl<Button>("scrollToLast");
-            _scrollToRandom = this.FindControl<Button>("scrollToRandom");
-            _scrollToSelected = this.FindControl<Button>("scrollToSelected");
+            _repeater = this.Get<ItemsRepeater>("repeater");
+            _scroller = this.Get<ScrollViewer>("scroller");
+            _scrollToLast = this.Get<Button>("scrollToLast");
+            _scrollToRandom = this.Get<Button>("scrollToRandom");
+            _scrollToSelected = this.Get<Button>("scrollToSelected");
             _repeater.PointerPressed += RepeaterClick;
             _repeater.KeyDown += RepeaterOnKeyDown;
             _scrollToLast.Click += scrollToLast_Click;
@@ -44,8 +44,10 @@ namespace ControlCatalog.Pages
 
         public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e)
         {
-            var item = (ItemsRepeaterPageViewModel.Item)e.DataContext;
-            e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
+            if (e.DataContext is ItemsRepeaterPageViewModel.Item item)
+            {
+                e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
+            }
         }
 
         private void LayoutChanged(object sender, SelectionChangedEventArgs e)
@@ -115,7 +117,7 @@ namespace ControlCatalog.Pages
         private void ScrollTo(int index)
         {
             System.Diagnostics.Debug.WriteLine("Scroll to " + index);
-            var layoutManager = ((TopLevel)VisualRoot).LayoutManager;
+            var layoutManager = ((TopLevel)VisualRoot!).LayoutManager;
             var element = _repeater.GetOrCreateElement(index);
             layoutManager.ExecuteLayoutPass();
             element.BringIntoView();
@@ -123,9 +125,11 @@ namespace ControlCatalog.Pages
 
         private void RepeaterClick(object sender, PointerPressedEventArgs e)
         {
-            var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item;
-            _viewModel.SelectedItem = item;
-            _selectedIndex = _viewModel.Items.IndexOf(item);
+            if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModel.Item item)
+            {
+                _viewModel.SelectedItem = item;
+                _selectedIndex = _viewModel.Items.IndexOf(item);
+            }
         }
 
         private void RepeaterOnKeyDown(object sender, KeyEventArgs e)

+ 1 - 1
samples/ControlCatalog/Pages/LabelsPage.axaml.cs

@@ -7,7 +7,7 @@ namespace ControlCatalog.Pages
 {
     public class LabelsPage : UserControl
     {
-        private Person _person;
+        private Person? _person;
 
         public LabelsPage()
         {

+ 1 - 1
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@@ -22,7 +22,7 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
         
-        private MenuPageViewModel _model;
+        private MenuPageViewModel? _model;
         protected override void OnDataContextChanged(EventArgs e)
         {
             if (_model != null)

+ 68 - 0
samples/ControlCatalog/Pages/NativeEmbedPage.xaml

@@ -0,0 +1,68 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:local="using:ControlCatalog.Pages"
+             d:DesignHeight="800"
+             d:DesignWidth="400"
+             x:Class="ControlCatalog.Pages.NativeEmbedPage">
+  <DockPanel>
+    <Menu DockPanel.Dock="Top">
+      <MenuItem Header="Test">
+        <MenuItem Header="SubMenu">
+          <MenuItem Header="Item 1"/>
+          <MenuItem Header="Item 2"/>
+          <MenuItem Header="Item 3"/>
+        </MenuItem>
+        <MenuItem Header="Item 1"/>
+        <MenuItem Header="Item 2"/>
+        <MenuItem Header="Item 3"/>
+      </MenuItem>
+    </Menu>
+    <DockPanel DockPanel.Dock="Top">
+      <Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
+      <Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
+      <Border DockPanel.Dock="Right" Background="#c0c0c0">
+        <ToolTip.Tip>
+          <ToolTip>
+            <TextBlock>Text</TextBlock>
+          </ToolTip>
+        </ToolTip.Tip>
+        <TextBlock VerticalAlignment="Center">Tooltip</TextBlock>
+      </Border>
+      <TextBox Text="Lorem ipsum dolor sit amet"/>
+
+    </DockPanel>
+    <Grid ColumnDefinitions="*,5,*"
+          RowDefinitions="*,5,*">
+      <Grid.Styles>
+        <Style Selector="DockPanel#FirstPanel:not(.mobile), DockPanel#SecondPanel:not(.mobile)">
+          <Setter Property="Grid.RowSpan" Value="3" />
+        </Style>
+        <Style Selector="DockPanel#SecondPanel:not(.mobile)">
+          <Setter Property="Grid.Column" Value="2" />
+        </Style>
+
+        <Style Selector="DockPanel#FirstPanel.mobile, DockPanel#SecondPanel.mobile">
+          <Setter Property="Grid.ColumnSpan" Value="3" />
+        </Style>
+        <Style Selector="DockPanel#SecondPanel.mobile">
+          <Setter Property="Grid.Row" Value="2" />
+        </Style>
+      </Grid.Styles>
+
+      <DockPanel x:Name="FirstPanel">
+        <CheckBox x:Name="firstVisible" DockPanel.Dock="Top"
+                  IsChecked="True" Content="Visible" />
+        <local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
+      </DockPanel>
+      <GridSplitter Grid.Row="0" Grid.RowSpan="3" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
+      <GridSplitter Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" Height="5" VerticalAlignment="Stretch" />
+      <DockPanel x:Name="SecondPanel">
+        <CheckBox x:Name="secondVisible" DockPanel.Dock="Top"
+                  IsChecked="True" Content="Visible" />
+        <local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
+      </DockPanel>
+    </Grid>
+  </DockPanel>
+</UserControl>

+ 86 - 0
samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Avalonia.Platform;
+using Avalonia.Interactivity;
+using Avalonia.Controls;
+using Avalonia.Controls.Platform;
+using Avalonia.Markup.Xaml;
+using Avalonia;
+
+namespace ControlCatalog.Pages
+{
+    public class NativeEmbedPage : UserControl
+    {
+        public NativeEmbedPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+        public async void ShowPopupDelay(object sender, RoutedEventArgs args)
+        {
+            await Task.Delay(3000);
+            ShowPopup(sender, args);
+        }
+
+        public void ShowPopup(object sender, RoutedEventArgs args)
+        {
+            new ContextMenu()
+            {
+                Items = new List<MenuItem>
+            {
+                new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
+            }
+            }.Open((Control)sender);
+        }
+
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            base.OnPropertyChanged(change);
+
+            if (change.Property == BoundsProperty)
+            {
+                var isMobile = change.GetNewValue<Rect>().Width < 1200;
+                this.Find<DockPanel>("FirstPanel")!.Classes.Set("mobile", isMobile);
+                this.Find<DockPanel>("SecondPanel")!.Classes.Set("mobile", isMobile);
+            }
+        }
+    }
+
+    public class EmbedSample : NativeControlHost
+    {
+        public static INativeDemoControl? Implementation { get; set; }
+
+        static EmbedSample()
+        {
+
+        }
+
+        public bool IsSecond { get; set; }
+
+        protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
+        {
+            return Implementation?.CreateControl(IsSecond, parent, () => base.CreateNativeControlCore(parent))
+                ?? base.CreateNativeControlCore(parent);
+        }
+
+        protected override void DestroyNativeControlCore(IPlatformHandle control)
+        {
+            base.DestroyNativeControlCore(control);
+        }
+    }
+
+    public interface INativeDemoControl
+    {
+        /// <param name="isSecond">Used to specify which control should be displayed as a demo</param>
+        /// <param name="parent"></param>
+        /// <param name="createDefault"></param>
+        IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault);
+    }
+}

+ 1 - 1
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@@ -65,7 +65,7 @@
                        Margin="2" HorizontalAlignment="Center"/>
 
         <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
-        <NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
+        <NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding DecimalValue}" VerticalAlignment="Center"
                        Margin="2" HorizontalAlignment="Center"/>
 
 

+ 5 - 5
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@@ -28,16 +28,16 @@ namespace ControlCatalog.Pages
 
     public class NumbersPageViewModel : ViewModelBase
     {
-        private IList<FormatObject> _formats;
+        private IList<FormatObject>? _formats;
         private FormatObject _selectedFormat;
-        private IList<Location> _spinnerLocations;
+        private IList<Location>? _spinnerLocations;
 
         private double _doubleValue;
         private decimal _decimalValue;
 
         public NumbersPageViewModel()
         {
-            SelectedFormat = Formats.FirstOrDefault();
+            _selectedFormat = Formats.FirstOrDefault();
         }
 
         public double DoubleValue
@@ -103,7 +103,7 @@ namespace ControlCatalog.Pages
 
     public class FormatObject
     {
-        public string Value { get; set; }
-        public string Name { get; set; }
+        public string? Value { get; set; }
+        public string? Name { get; set; }
     }
 }

+ 1 - 1
samples/ControlCatalog/Pages/ScreenPage.cs

@@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
             base.OnAttachedToVisualTree(e);
-            Window w = (Window)VisualRoot;
+            Window w = (Window)VisualRoot!;
             w.PositionChanged += (sender, args) => InvalidateVisual();
         }
 

+ 6 - 6
samples/ControlCatalog/Pages/TabControlPage.xaml.cs

@@ -52,7 +52,7 @@ namespace ControlCatalog.Pages
 
         private IBitmap LoadBitmap(string uri)
         {
-            var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
+            var assets = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
             return new Bitmap(assets.Open(new Uri(uri)));
         }
 
@@ -60,7 +60,7 @@ namespace ControlCatalog.Pages
         {
             private Dock _tabPlacement;
 
-            public TabItemViewModel[] Tabs { get; set; }
+            public TabItemViewModel[]? Tabs { get; set; }
 
             public Dock TabPlacement
             {
@@ -71,10 +71,10 @@ namespace ControlCatalog.Pages
 
         private class TabItemViewModel
         {
-            public string Header { get; set; }
-            public string Text { get; set; }
-            public IBitmap Image { get; set; }
-            public bool IsEnabled { get; set; } = true;           
+            public string? Header { get; set; }
+            public string? Text { get; set; }
+            public IBitmap? Image { get; set; }
+            public bool IsEnabled { get; set; } = true;
         }
     }
 }

+ 1 - 1
samples/ControlCatalog/Pages/TabStripPage.xaml.cs

@@ -38,7 +38,7 @@ namespace ControlCatalog.Pages
 
         private class TabStripItemViewModel
         {
-            public string Header { get; set; }
+            public string? Header { get; set; }
             public bool IsEnabled { get; set; } = true;
         }
     }

+ 1 - 1
samples/ControlCatalog/ViewModels/ApplicationViewModel.cs

@@ -10,7 +10,7 @@ namespace ControlCatalog.ViewModels
         {
             ExitCommand = MiniCommand.Create(() =>
             {
-                if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
+                if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
                 {
                     lifetime.Shutdown();
                 }

+ 1 - 1
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@@ -9,7 +9,7 @@ namespace ControlCatalog.ViewModels
 {
     public class ContextPageViewModel
     {
-        public Control View { get; set; }
+        public Control? View { get; set; }
         public ContextPageViewModel()
         {
             OpenCommand = MiniCommand.CreateFromTask(Open);

+ 1 - 1
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels
                 .Select(x => new StandardCursorModel(x))
                 .ToList();
 
-            var loader = AvaloniaLocator.Current.GetService<IAssetLoader>();
+            var loader = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
             var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png"));
             var bitmap = new Bitmap(s);
             CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));

+ 10 - 9
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@@ -14,7 +14,7 @@ namespace ControlCatalog.ViewModels
 
         public ItemsRepeaterPageViewModel()
         {
-            Items = CreateItems();
+            _items = CreateItems();
         }
 
         public ObservableCollection<Item> Items
@@ -23,12 +23,12 @@ namespace ControlCatalog.ViewModels
             set => this.RaiseAndSetIfChanged(ref _items, value);
         }
 
-        public Item SelectedItem { get; set; }
+        public Item? SelectedItem { get; set; }
 
         public void AddItem()
         {
             var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
-            Items.Insert(index + 1, new Item(index + 1) { Text = $"New Item {_newItemIndex++}" });
+            Items.Insert(index + 1, new Item(index + 1, $"New Item {_newItemIndex++}"));
         }
 
         public void RemoveItem()
@@ -66,19 +66,20 @@ namespace ControlCatalog.ViewModels
             _newGenerationIndex++;
 
             return new ObservableCollection<Item>(
-                Enumerable.Range(1, 100000).Select(i => new Item(i)
-                {
-                    Text = $"Item {i.ToString()} {suffix}"
-                }));
+                Enumerable.Range(1, 100000).Select(i => new Item(i, $"Item {i.ToString()} {suffix}")));
         }
 
         public class Item : ViewModelBase
         {
             private double _height = double.NaN;
 
-            public Item(int index) => Index = index;
+            public Item(int index, string text)
+            {
+                Index = index;
+                Text = text;
+            }
             public int Index { get; }
-            public string Text { get; set; }
+            public string Text { get; }
             
             public double Height 
             {

+ 7 - 6
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@@ -16,11 +16,11 @@ namespace ControlCatalog.ViewModels
 
         private bool _isMenuItemChecked = true;
         private WindowState _windowState;
-        private WindowState[] _windowStates;
+        private WindowState[] _windowStates = Array.Empty<WindowState>();
         private int _transparencyLevel;
         private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
         private bool _extendClientAreaEnabled;
-        private bool _systemTitleBarEnabled;        
+        private bool _systemTitleBarEnabled;
         private bool _preferSystemChromeEnabled;
         private double _titleBarHeight;
 
@@ -47,14 +47,15 @@ namespace ControlCatalog.ViewModels
             {
                 var dialog = new AboutAvaloniaDialog();
 
-                var mainWindow = (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
-
-                await dialog.ShowDialog(mainWindow);
+                if ((App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow is { } mainWindow)
+                {
+                    await dialog.ShowDialog(mainWindow);
+                }
             });
 
             ExitCommand = MiniCommand.Create(() =>
             {
-                (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown();
+                (App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown();
             });
 
             ToggleMenuItemCheckedCommand = MiniCommand.Create(() =>

+ 4 - 4
samples/ControlCatalog/ViewModels/MenuItemViewModel.cs

@@ -5,9 +5,9 @@ namespace ControlCatalog.ViewModels
 {
     public class MenuItemViewModel
     {
-        public string Header { get; set; }
-        public ICommand Command { get; set; }
-        public object CommandParameter { get; set; }
-        public IList<MenuItemViewModel> Items { get; set; }
+        public string? Header { get; set; }
+        public ICommand? Command { get; set; }
+        public object? CommandParameter { get; set; }
+        public IList<MenuItemViewModel>? Items { get; set; }
     }
 }

+ 1 - 1
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@@ -10,7 +10,7 @@ namespace ControlCatalog.ViewModels
 {
     public class MenuPageViewModel
     {
-        public Control View { get; set; }
+        public Control? View { get; set; }
         public MenuPageViewModel()
         {
             OpenCommand = MiniCommand.CreateFromTask(Open);

+ 2 - 2
samples/ControlCatalog/ViewModels/NotificationViewModel.cs

@@ -19,8 +19,8 @@ namespace ControlCatalog.ViewModels
             });
         }
 
-        public string Title { get; set; }
-        public string Message { get; set; }
+        public string? Title { get; set; }
+        public string? Message { get; set; }
 
         public MiniCommand YesCommand { get; }
 

+ 8 - 8
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

@@ -19,7 +19,7 @@ namespace ControlCatalog.ViewModels
     {
         public TransitioningContentControlPageViewModel()
         {
-            var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
+            var assetLoader = AvaloniaLocator.Current?.GetService<IAssetLoader>()!;
 
             var images = new string[] 
             { 
@@ -36,8 +36,8 @@ namespace ControlCatalog.ViewModels
 
             SetupTransitions();
 
-            SelectedTransition = PageTransitions[1];
-            SelectedImage = Images[0];
+            _SelectedTransition = PageTransitions[1];
+            _SelectedImage = Images[0];
         }
 
         public List<PageTransition> PageTransitions { get; } = new List<PageTransition>();
@@ -45,12 +45,12 @@ namespace ControlCatalog.ViewModels
         public List<Bitmap> Images { get; } = new List<Bitmap>();
 
 
-        private Bitmap? _SelectedImage;
+        private Bitmap _SelectedImage;
 
         /// <summary>
         /// Gets or Sets the selected image
         /// </summary>
-        public Bitmap? SelectedImage
+        public Bitmap SelectedImage
         {
             get { return _SelectedImage; }
             set { this.RaiseAndSetIfChanged(ref _SelectedImage, value); }
@@ -160,12 +160,12 @@ namespace ControlCatalog.ViewModels
         public string DisplayTitle { get; }
 
 
-        private IPageTransition _Transition;
+        private IPageTransition? _Transition;
 
         /// <summary>
         /// Gets or sets the transition
         /// </summary>
-        public IPageTransition Transition
+        public IPageTransition? Transition
         {
             get { return _Transition; }
             set { this.RaiseAndSetIfChanged(ref _Transition, value); }
@@ -201,7 +201,7 @@ namespace ControlCatalog.ViewModels
         /// </summary>
         public TimeSpan Duration { get; set; }
 
-        public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
+        public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
         {
             if (cancellationToken.IsCancellationRequested)
             {

+ 2 - 2
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@@ -92,7 +92,7 @@ namespace ControlCatalog.ViewModels
 
         public class Node
         {
-            private ObservableCollection<Node> _children;
+            private ObservableCollection<Node>? _children;
             private int _childIndex = 10;
 
             public Node()
@@ -106,7 +106,7 @@ namespace ControlCatalog.ViewModels
                 Header = parent.Header + ' ' + index;
             }
 
-            public Node Parent { get; }
+            public Node? Parent { get; }
             public string Header { get; }
             public bool AreChildrenInitialized => _children != null;
             public ObservableCollection<Node> Children => _children ??= CreateChildren();

+ 0 - 8
samples/interop/NativeEmbedSample/App.xaml

@@ -1,8 +0,0 @@
-<Application xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="NativeEmbedSample.App">
-  <Application.Styles>
-    <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
-    <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
-  </Application.Styles>
-</Application>

+ 0 - 22
samples/interop/NativeEmbedSample/App.xaml.cs

@@ -1,22 +0,0 @@
-using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Markup.Xaml;
-
-namespace NativeEmbedSample
-{
-    public class App : Application
-    {
-        public override void Initialize()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
-
-        public override void OnFrameworkInitializationCompleted()
-        {
-            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
-                desktopLifetime.MainWindow = new MainWindow();
-
-            base.OnFrameworkInitializationCompleted();
-        }
-    }
-}

+ 0 - 121
samples/interop/NativeEmbedSample/EmbedSample.cs

@@ -1,121 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Text;
-using Avalonia.Controls;
-using Avalonia.Platform;
-using Avalonia.Threading;
-using MonoMac.AppKit;
-using MonoMac.Foundation;
-using MonoMac.WebKit;
-using Encoding = SharpDX.Text.Encoding;
-
-namespace NativeEmbedSample
-{
-    public class EmbedSample : NativeControlHost
-    {
-        public bool IsSecond { get; set; }
-        private Process _mplayer;
-
-        IPlatformHandle CreateLinux(IPlatformHandle parent)
-        {
-            if (IsSecond)
-            {
-                var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
-                if (chooser != null)
-                    return chooser;
-            }
-
-            var control = base.CreateNativeControlCore(parent);
-            var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
-                "..",
-                "nodes.mp4"));
-            _mplayer = Process.Start(new ProcessStartInfo("mplayer",
-                $"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
-            {
-                UseShellExecute = false,
-
-            });
-            return control;
-        }
-
-        void DestroyLinux(IPlatformHandle handle)
-        {
-            _mplayer?.Kill();
-            _mplayer = null;
-            base.DestroyNativeControlCore(handle);
-        }
-
-        private const string RichText =
-            @"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
-{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
-{\*\generator Riched20 6.3.9600}\viewkind4\uc1 
-\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0  a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
-}";
-
-        IPlatformHandle CreateWin32(IPlatformHandle parent)
-        {
-            WinApi.LoadLibrary("Msftedit.dll");
-            var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
-                @"Rich Edit",
-                0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
-                IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
-            var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
-            var text = RichText.Replace("<PREFIX>", IsSecond ? "\\qr " : "");
-            var bytes = Encoding.UTF8.GetBytes(text);
-            WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
-            return new PlatformHandle(handle, "HWND");
-
-        }
-
-        void DestroyWin32(IPlatformHandle handle)
-        {
-            WinApi.DestroyWindow(handle.Handle);
-        }
-
-        IPlatformHandle CreateOSX(IPlatformHandle parent)
-        {
-            // Note: We are using MonoMac for example purposes
-            // It shouldn't be used in production apps
-            MacHelper.EnsureInitialized();
-
-            var webView = new WebView();
-            Dispatcher.UIThread.Post(() =>
-            {
-                webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
-                    IsSecond ? "https://bing.com": "https://google.com/")));
-            });
-            return new MacOSViewHandle(webView);
-
-        }
-
-        void DestroyOSX(IPlatformHandle handle)
-        {
-            ((MacOSViewHandle)handle).Dispose();
-        }
-        
-        protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
-        {
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-                return CreateLinux(parent);
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-                return CreateWin32(parent);
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-                return CreateOSX(parent);
-            return base.CreateNativeControlCore(parent);
-        }
-
-        protected override void DestroyNativeControlCore(IPlatformHandle control)
-        {
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-                DestroyLinux(control);
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-                DestroyWin32(control);
-            else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-                DestroyOSX(control);
-            else
-                base.DestroyNativeControlCore(control);
-        }
-    }
-}

+ 0 - 58
samples/interop/NativeEmbedSample/GtkHelper.cs

@@ -1,58 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Avalonia.Controls.Platform;
-using Avalonia.Platform;
-using Avalonia.Platform.Interop;
-using Avalonia.X11.NativeDialogs;
-using static Avalonia.X11.NativeDialogs.Gtk;
-using static Avalonia.X11.NativeDialogs.Glib;
-namespace NativeEmbedSample
-{
-    public class GtkHelper
-    {
-        private static  Task<bool> s_gtkTask;
-        class FileChooser : INativeControlHostDestroyableControlHandle
-        {
-            private readonly IntPtr _widget;
-
-            public FileChooser(IntPtr widget, IntPtr xid)
-            {
-                _widget = widget;
-                Handle = xid;
-            }
-
-            public IntPtr Handle { get; }
-            public string HandleDescriptor => "XID";
-            public void Destroy()
-            {
-                RunOnGlibThread(() =>
-                {
-                    gtk_widget_destroy(_widget);
-                    return 0;
-                }).Wait();
-            }
-        }
-
-        
-        
-        public static IPlatformHandle CreateGtkFileChooser(IntPtr parentXid)
-        {
-            if (s_gtkTask == null)
-                s_gtkTask = StartGtk();
-            if (!s_gtkTask.Result)
-                return null;
-            return RunOnGlibThread(() =>
-            {
-                using (var title = new Utf8Buffer("Embedded"))
-                {
-                    var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
-                        IntPtr.Zero);
-                    gtk_widget_realize(widget);
-                    var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
-                    gtk_window_present(widget);
-                    return new FileChooser(widget,  xid);
-                }
-            }).Result;
-        }
-    }
-}

+ 0 - 39
samples/interop/NativeEmbedSample/MacHelper.cs

@@ -1,39 +0,0 @@
-using System;
-using Avalonia.Platform;
-using MonoMac.AppKit;
-
-namespace NativeEmbedSample
-{
-    public class MacHelper
-    {
-        private static bool _isInitialized;
-
-        public static void EnsureInitialized()
-        {
-            if (_isInitialized)
-                return;
-            _isInitialized = true;
-            NSApplication.Init();
-        }
-    }
-
-    class MacOSViewHandle : IPlatformHandle, IDisposable
-    {
-        private NSView _view;
-
-        public MacOSViewHandle(NSView view)
-        {
-            _view = view;
-        }
-
-        public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
-        public string HandleDescriptor => "NSView";
-
-        public void Dispose()
-        {
-            _view.Dispose();
-            _view = null;
-        }
-    }
-
-}

+ 0 - 52
samples/interop/NativeEmbedSample/MainWindow.xaml

@@ -1,52 +0,0 @@
-<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
-        Width="1024" Height="800"
-        Title="Native embedding sample"
-        xmlns:local="clr-namespace:NativeEmbedSample"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        x:Class="NativeEmbedSample.MainWindow">
-  <DockPanel>
-    <Menu DockPanel.Dock="Top">
-      <MenuItem Header="Test">
-        <MenuItem Header="SubMenu">
-          <MenuItem Header="Item 1"/>
-          <MenuItem Header="Item 2"/>
-          <MenuItem Header="Item 3"/>  
-        </MenuItem>
-        <MenuItem Header="Item 1"/>
-        <MenuItem Header="Item 2"/>
-        <MenuItem Header="Item 3"/>  
-      </MenuItem>
-    </Menu>
-    <DockPanel DockPanel.Dock="Top">
-      <Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
-      <Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
-      <Border DockPanel.Dock="Right" Background="#c0c0c0">
-        <ToolTip.Tip>
-          <ToolTip>
-              <TextBlock>Text</TextBlock>
-          </ToolTip>
-        </ToolTip.Tip>
-        <TextBlock>Tooltip</TextBlock>
-      </Border>
-      <TextBox Text="Lorem ipsum dolor sit amet"/>
-      
-    </DockPanel>
-    <Grid ColumnDefinitions="*,5,*">
-      <DockPanel>
-        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
-          <CheckBox x:Name="firstVisible" IsChecked="True"/>
-          <TextBlock>Visible</TextBlock>
-        </StackPanel>
-        <local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
-      </DockPanel>
-      <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
-      <DockPanel Grid.Column="2">
-        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
-          <CheckBox x:Name="secondVisible" IsChecked="True"/>
-          <TextBlock>Visible</TextBlock>
-        </StackPanel>
-        <local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
-      </DockPanel>
-    </Grid>
-  </DockPanel>
-</Window>

+ 0 - 36
samples/interop/NativeEmbedSample/MainWindow.xaml.cs

@@ -1,36 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
-
-namespace NativeEmbedSample
-{
-    public class MainWindow : Window
-    {
-        public MainWindow()
-        {
-            AvaloniaXamlLoader.Load(this);
-            this.AttachDevTools();
-        }
-
-        public async void ShowPopupDelay(object sender, RoutedEventArgs args)
-        {
-            await Task.Delay(3000);
-            ShowPopup(sender, args);
-        }
-
-        public void ShowPopup(object sender, RoutedEventArgs args)
-        {
-
-            new ContextMenu()
-            {
-                Items = new List<MenuItem>
-                {
-                    new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
-                }
-            }.Open((Control)sender);
-        }
-    }
-}

+ 0 - 31
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@@ -1,31 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>netcoreapp2.0</TargetFramework>
-    <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
-    <ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
-    <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
-    <ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
-    <ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
-    <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
-
-    <AvaloniaResource Include="**\*.xaml">
-      <SubType>Designer</SubType>
-    </AvaloniaResource>
-    <None Remove="nodes.mp4" />
-    <Content Include="nodes.mp4">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Compile Include="..\..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" />
-  </ItemGroup>
-
-  <Import Project="..\..\..\build\SampleApp.props" />
-  <Import Project="..\..\..\build\BuildTargets.targets" />
-  <Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
-</Project>

+ 0 - 17
samples/interop/NativeEmbedSample/Program.cs

@@ -1,17 +0,0 @@
-using Avalonia;
-
-namespace NativeEmbedSample
-{
-    class Program
-    {
-        static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
-
-        public static AppBuilder BuildAvaloniaApp()
-            => AppBuilder.Configure<App>()
-                .With(new AvaloniaNativePlatformOptions()
-                {
-                })
-                .UsePlatformDetect();
-
-    }
-}

+ 0 - 74
samples/interop/NativeEmbedSample/WinApi.cs

@@ -1,74 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace NativeEmbedSample
-{
-    public unsafe class WinApi
-    {
-        public enum CommonControls : uint
-        {
-            ICC_LISTVIEW_CLASSES   = 0x00000001, // listview, header
-            ICC_TREEVIEW_CLASSES   = 0x00000002, // treeview, tooltips
-            ICC_BAR_CLASSES    = 0x00000004, // toolbar, statusbar, trackbar, tooltips
-            ICC_TAB_CLASSES    = 0x00000008, // tab, tooltips
-            ICC_UPDOWN_CLASS       = 0x00000010, // updown
-            ICC_PROGRESS_CLASS     = 0x00000020, // progress
-            ICC_HOTKEY_CLASS       = 0x00000040, // hotkey
-            ICC_ANIMATE_CLASS      = 0x00000080, // animate
-            ICC_WIN95_CLASSES      = 0x000000FF,
-            ICC_DATE_CLASSES       = 0x00000100, // month picker, date picker, time picker, updown
-            ICC_USEREX_CLASSES     = 0x00000200, // comboex
-            ICC_COOL_CLASSES       = 0x00000400, // rebar (coolbar) control
-            ICC_INTERNET_CLASSES   = 0x00000800,
-            ICC_PAGESCROLLER_CLASS = 0x00001000,  // page scroller
-            ICC_NATIVEFNTCTL_CLASS = 0x00002000,  // native font control
-            ICC_STANDARD_CLASSES   = 0x00004000,
-            ICC_LINK_CLASS     = 0x00008000
-        }
-
-        [StructLayout(LayoutKind.Sequential)]
-        public struct INITCOMMONCONTROLSEX
-        {
-            public int dwSize;
-            public uint dwICC;
-        }
-
-        [DllImport("Comctl32.dll")]
-        public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
-
-        [DllImport("user32.dll", SetLastError = true)]
-        public static extern bool DestroyWindow(IntPtr hwnd);
-
-        [DllImport("kernel32.dll")]
-        public static extern IntPtr LoadLibrary(string lib);
-
-
-        [DllImport("kernel32.dll")]
-        public static extern IntPtr GetModuleHandle(string lpModuleName);
-
-        [DllImport("user32.dll", SetLastError = true)]
-        public static extern IntPtr CreateWindowEx(
-            int dwExStyle,
-            string lpClassName,
-            string lpWindowName,
-            uint dwStyle,
-            int x,
-            int y,
-            int nWidth,
-            int nHeight,
-            IntPtr hWndParent,
-            IntPtr hMenu,
-            IntPtr hInstance,
-            IntPtr lpParam);
-
-        [StructLayout(LayoutKind.Sequential)]
-        public struct SETTEXTEX
-        {
-            public uint Flags;
-            public uint Codepage;
-        }
-
-        [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
-        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
-    }
-}

+ 32 - 0
src/Android/Avalonia.Android/AndroidViewControlHandle.cs

@@ -0,0 +1,32 @@
+#nullable enable
+
+using System;
+
+using Android.Views;
+
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+
+namespace Avalonia.Android
+{
+    public class AndroidViewControlHandle : INativeControlHostDestroyableControlHandle
+    {
+        internal const string AndroidDescriptor = "JavaObjectHandle";
+
+        public AndroidViewControlHandle(View view)
+        {
+            View = view;
+        }
+
+        public View View { get; }
+
+        public string HandleDescriptor => AndroidDescriptor;
+
+        IntPtr IPlatformHandle.Handle => View.Handle;
+
+        public void Destroy()
+        {
+            View?.Dispose();
+        }
+    }
+}

+ 5 - 4
src/Android/Avalonia.Android/AvaloniaView.cs

@@ -15,13 +15,12 @@ namespace Avalonia.Android
         private EmbeddableControlRoot _root;
         private readonly ViewImpl _view;
 
-        private IDisposable? _timerSubscription;
+        private IDisposable _timerSubscription;
 
         public AvaloniaView(Context context) : base(context)
         {
-            _view = new ViewImpl(context);
+            _view = new ViewImpl(this);
             AddView(_view.View);
-            
         }
 
         internal void Prepare ()
@@ -30,6 +29,8 @@ namespace Avalonia.Android
             _root.Prepare();
         }
 
+        internal TopLevelImpl TopLevelImpl => _view;
+
         public object Content
         {
             get { return _root.Content; }
@@ -73,7 +74,7 @@ namespace Avalonia.Android
 
         class ViewImpl : TopLevelImpl
         {
-            public ViewImpl(Context context) : base(context)
+            public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView)
             {
                 View.Focusable = true;
                 View.FocusChange += ViewImpl_FocusChange;

+ 139 - 0
src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs

@@ -0,0 +1,139 @@
+#nullable enable
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+using Android.Views;
+using Android.Widget;
+
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+
+namespace Avalonia.Android.Platform
+{
+    internal class AndroidNativeControlHostImpl : INativeControlHostImpl
+    {
+        private readonly AvaloniaView _avaloniaView;
+
+        public AndroidNativeControlHostImpl(AvaloniaView avaloniaView)
+        {
+            _avaloniaView = avaloniaView;
+        }
+
+        public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
+        {
+            return new AndroidViewControlHandle(new FrameLayout(_avaloniaView.Context!));
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
+        {
+            var parent = new AndroidViewControlHandle(_avaloniaView);
+            AndroidNativeControlAttachment? attachment = null;
+            try
+            {
+                var child = create(parent);
+                // It has to be assigned to the variable before property setter is called so we dispose it on exception
+#pragma warning disable IDE0017 // Simplify object initialization
+                attachment = new AndroidNativeControlAttachment(child);
+#pragma warning restore IDE0017 // Simplify object initialization
+                attachment.AttachedTo = this;
+                return attachment;
+            }
+            catch
+            {
+                attachment?.Dispose();
+                throw;
+            }
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
+        {
+            return new AndroidNativeControlAttachment(handle)
+            {
+                AttachedTo = this
+            };
+        }
+
+        public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidDescriptor;
+
+        private class AndroidNativeControlAttachment : INativeControlHostControlTopLevelAttachment
+        {
+            private View? _view;
+            private AndroidNativeControlHostImpl? _attachedTo;
+
+            public AndroidNativeControlAttachment(IPlatformHandle child)
+            {
+                _view = (child as AndroidViewControlHandle)?.View
+                    ?? Java.Lang.Object.GetObject<View>(child.Handle, global::Android.Runtime.JniHandleOwnership.DoNotTransfer);
+            }
+
+            [MemberNotNull(nameof(_view))]
+            private void CheckDisposed()
+            {
+                if (_view == null)
+                    throw new ObjectDisposedException(nameof(AndroidNativeControlAttachment));
+            }
+
+            public void Dispose()
+            {
+                if (_view != null && _attachedTo?._avaloniaView is ViewGroup parent)
+                {
+                    parent.RemoveView(_view);
+                }
+                _attachedTo = null;
+                _view?.Dispose();
+                _view = null;
+            }
+
+            public INativeControlHostImpl? AttachedTo
+            {
+                get => _attachedTo;
+                set
+                {
+                    CheckDisposed();
+
+                    var oldAttachedTo = _attachedTo;
+                    _attachedTo = (AndroidNativeControlHostImpl?)value;
+                    if (_attachedTo == null)
+                    {
+                        oldAttachedTo?._avaloniaView.RemoveView(_view);
+                    }
+                    else
+                    {
+                        _attachedTo._avaloniaView.AddView(_view);
+                    }
+                }
+            }
+
+            public bool IsCompatibleWith(INativeControlHostImpl host) => host is AndroidNativeControlHostImpl;
+
+            public void HideWithSize(Size size)
+            {
+                CheckDisposed();
+                if (_attachedTo == null)
+                    return;
+
+                size *= _attachedTo._avaloniaView.TopLevelImpl.RenderScaling;
+                _view.Visibility = ViewStates.Gone;
+                _view.LayoutParameters = new FrameLayout.LayoutParams(Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
+                _view.RequestLayout();
+            }
+
+            public void ShowInBounds(Rect bounds)
+            {
+                CheckDisposed();
+                if (_attachedTo == null)
+                    throw new InvalidOperationException("The control isn't currently attached to a toplevel");
+
+                bounds *= _attachedTo._avaloniaView.TopLevelImpl.RenderScaling;
+                _view.Visibility = ViewStates.Visible;
+                _view.LayoutParameters = new FrameLayout.LayoutParams(Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height))
+                {
+                    LeftMargin = (int)bounds.X,
+                    TopMargin = (int)bounds.Y
+                };
+                _view.RequestLayout();
+            }
+        }
+    }
+}

+ 7 - 3
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -20,7 +20,7 @@ using Avalonia.Rendering;
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
-    class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod
+    class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost
     {
         private readonly IGlPlatformSurface _gl;
         private readonly IFramebufferPlatformSurface _framebuffer;
@@ -30,9 +30,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         private readonly ITextInputMethodImpl _textInputMethod;
         private ViewImpl _view;
 
-        public TopLevelImpl(Context context, bool placeOnTop = false)
+        public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
         {
-            _view = new ViewImpl(context, this, placeOnTop);
+            _view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
             _textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
             _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
             _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
@@ -44,6 +44,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
             MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
                 _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
+
+            NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
         }
 
         public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@@ -222,6 +224,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public ITextInputMethodImpl TextInputMethod => _textInputMethod;
 
+        public INativeControlHostImpl NativeControlHost { get; }
+
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
         {
             throw new NotImplementedException();

+ 1 - 1
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@@ -30,7 +30,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
             return DispatchKeyEventInternal(e, out callBase);
         }
 
-        string? UnicodeTextInput(KeyEvent keyEvent)
+        string UnicodeTextInput(KeyEvent keyEvent)
         {
             return keyEvent.Action == KeyEventActions.Multiple
                 && keyEvent.RepeatCount == 0

+ 8 - 1
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@@ -33,7 +33,14 @@ namespace Avalonia.Data.Converters
 
             if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)
             {
-                return new MethodToCommandConverter(d);
+                if (d.Method.IsPrivate == false)
+                {
+                    return new MethodToCommandConverter(d);
+                }
+                else
+                {
+                    return new BindingNotification(new InvalidCastException("You can't bind to private methods!"), BindingErrorType.Error);
+                }
             }
 
             if (TypeUtilities.TryConvert(targetType, value, culture, out var result))

+ 38 - 38
src/Avalonia.Base/Matrix.cs

@@ -9,14 +9,14 @@ namespace Avalonia
     /// <summary>
     /// A 3x3 matrix.
     /// </summary>
-    /// <remakrs>Matrix layout:
+    /// <remarks>Matrix layout:
     ///         | 1st col | 2nd col | 3r col |
-    /// 1st row | scaleX  | skrewY  | persX  |
-    /// 2nd row | skrewX  | scaleY  | persY  |
-    /// 3rd row | transX  | transY  | persZ  |
+    /// 1st row | scaleX  | skewY  | perspX  |
+    /// 2nd row | skewX  | scaleY  | perspY  |
+    /// 3rd row | transX  | transY  | perspZ  |
     /// 
-    /// Note: Skia.SkMatrix uses a transposed layout (where for example skrewX/skrewY and perspp0/tranX are swapped).
-    /// </remakrs>
+    /// Note: Skia.SkMatrix uses a transposed layout (where for example skewX/skewY and persp0/transX are swapped).
+    /// </remarks>
 #if !BUILDTASK
     public
 #endif
@@ -36,18 +36,18 @@ namespace Avalonia
         /// Initializes a new instance of the <see cref="Matrix"/> struct (equivalent to a 2x3 Matrix without perspective).
         /// </summary>
         /// <param name="scaleX">The first element of the first row.</param>
-        /// <param name="skrewY">The second element of the first row.</param>
-        /// <param name="skrewX">The first element of the second row.</param>
+        /// <param name="skewY">The second element of the first row.</param>
+        /// <param name="skewX">The first element of the second row.</param>
         /// <param name="scaleY">The second element of the second row.</param>
         /// <param name="offsetX">The first element of the third row.</param>
         /// <param name="offsetY">The second element of the third row.</param>
         public Matrix(
             double scaleX,
-            double skrewY,
-            double skrewX,
+            double skewY,
+            double skewX,
             double scaleY,
             double offsetX,
-            double offsetY) : this( scaleX, skrewY, 0, skrewX, scaleY, 0, offsetX, offsetY, 1)
+            double offsetY) : this( scaleX, skewY, 0, skewX, scaleY, 0, offsetX, offsetY, 1)
         {
         }
 
@@ -57,34 +57,34 @@ namespace Avalonia
         /// Initializes a new instance of the <see cref="Matrix"/> struct.
         /// </summary>
         /// <param name="scaleX">The first element of the first row.</param>
-        /// <param name="skrewY">The second element of the first row.</param>
-        /// <param name="persX">The third element of the first row.</param>
-        /// <param name="skrewX">The first element of the second row.</param>
+        /// <param name="skewY">The second element of the first row.</param>
+        /// <param name="perspX">The third element of the first row.</param>
+        /// <param name="skewX">The first element of the second row.</param>
         /// <param name="scaleY">The second element of the second row.</param>
-        /// <param name="persY">The third element of the second row.</param>
+        /// <param name="perspY">The third element of the second row.</param>
         /// <param name="offsetX">The first element of the third row.</param>
         /// <param name="offsetY">The second element of the third row.</param>
-        /// <param name="persZ">The third element of the third row.</param>
+        /// <param name="perspZ">The third element of the third row.</param>
         public Matrix(
             double scaleX,
-            double skrewY,
-            double persX,
-            double skrewX,
+            double skewY,
+            double perspX,
+            double skewX,
             double scaleY,
-            double persY, 
+            double perspY, 
             double offsetX,
             double offsetY,
-            double persZ)
+            double perspZ)
         {
             _m11 = scaleX;
-            _m12 = skrewY;
-            _m13 = persX;
-            _m21 = skrewX;
+            _m12 = skewY;
+            _m13 = perspX;
+            _m21 = skewX;
             _m22 = scaleY;
-            _m23 = persY;
+            _m23 = perspY;
             _m31 = offsetX;
             _m32 = offsetY;
-            _m33 = persZ;
+            _m33 = perspZ;
         }
 
         /// <summary>
@@ -111,17 +111,17 @@ namespace Avalonia
         public double M11 => _m11;
 
         /// <summary>
-        /// The second element of the first row (skrewY).
+        /// The second element of the first row (skewY).
         /// </summary>
         public double M12 => _m12;
 
         /// <summary>
-        /// The third element of the first row (persX: input x-axis perspective factor).
+        /// The third element of the first row (perspX: input x-axis perspective factor).
         /// </summary>
         public double M13 => _m13;
 
         /// <summary>
-        /// The first element of the second row (skrewX).
+        /// The first element of the second row (skewX).
         /// </summary>
         public double M21 => _m21;
 
@@ -131,7 +131,7 @@ namespace Avalonia
         public double M22 => _m22;
 
         /// <summary>
-        /// The third element of the second row (persY: input y-axis perspective factor).
+        /// The third element of the second row (perspY: input y-axis perspective factor).
         /// </summary>
         public double M23 => _m23;
 
@@ -146,7 +146,7 @@ namespace Avalonia
         public double M32 => _m32;
 
         /// <summary>
-        /// The third element of the third row (persZ: perspective scale factor).
+        /// The third element of the third row (perspZ: perspective scale factor).
         /// </summary>
         public double M33 => _m33;
 
@@ -450,13 +450,13 @@ namespace Avalonia
             
             inverted = new Matrix(
                 (_m22 * _m33 - _m32 * _m23) * invdet,
-                (_m13 * _m31 - _m12 * _m33) * invdet,
+                (_m13 * _m32 - _m12 * _m33) * invdet,
                 (_m12 * _m23 - _m13 * _m22) * invdet,
                 (_m23 * _m31 - _m21 * _m33) * invdet,
                 (_m11 * _m33 - _m13 * _m31) * invdet,
                 (_m21 * _m13 - _m11 * _m23) * invdet,
                 (_m21 * _m32 - _m31 * _m22) * invdet,
-                (_m21 * _m12 - _m11 * _m32) * invdet,
+                (_m31 * _m12 - _m11 * _m32) * invdet,
                 (_m11 * _m22 - _m21 * _m12) * invdet
                 );
             
@@ -481,7 +481,7 @@ namespace Avalonia
         /// <summary>
         /// Parses a <see cref="Matrix"/> string.
         /// </summary>
-        /// <param name="s">Six or nine comma-delimited double values (m11, m12, m21, m22, offsetX, offsetY[, persX, persY, persZ]) that describe the new <see cref="Matrix"/></param>
+        /// <param name="s">Six or nine comma-delimited double values (m11, m12, m21, m22, offsetX, offsetY[, perspX, perspY, perspZ]) that describe the new <see cref="Matrix"/></param>
         /// <returns>The <see cref="Matrix"/>.</returns>
         public static Matrix Parse(string s)
         {
@@ -497,11 +497,11 @@ namespace Avalonia
                 var v4 = tokenizer.ReadDouble();
                 var v5 = tokenizer.ReadDouble();
                 var v6 = tokenizer.ReadDouble();
-                var pers = tokenizer.TryReadDouble(out var v7);
-                pers = pers && tokenizer.TryReadDouble(out v8);
-                pers = pers && tokenizer.TryReadDouble(out v9);
+                var persp = tokenizer.TryReadDouble(out var v7);
+                persp = persp && tokenizer.TryReadDouble(out v8);
+                persp = persp && tokenizer.TryReadDouble(out v9);
 
-                if (pers) 
+                if (persp) 
                     return new Matrix(v1, v2, v7, v3, v4, v8, v5, v6, v9);
                 else
                     return new Matrix(v1, v2, v3, v4, v5, v6);

+ 2 - 1
src/Avalonia.Base/Media/GeometryDrawing.cs

@@ -68,7 +68,8 @@ namespace Avalonia.Media
 
         public override Rect GetBounds()
         {
-            return Geometry?.GetRenderBounds(s_boundsPen) ?? Rect.Empty;
+            IPen pen = Pen ?? s_boundsPen;
+			return Geometry?.GetRenderBounds(pen) ?? Rect.Empty;
         }
     }
 }

+ 9 - 2
src/Avalonia.Base/Media/TextFormatting/TextBounds.cs

@@ -10,20 +10,27 @@ namespace Avalonia.Media.TextFormatting
         /// <summary>
         /// Constructing TextBounds object
         /// </summary>
-        internal TextBounds(Rect bounds, FlowDirection flowDirection)
+        internal TextBounds(Rect bounds, FlowDirection flowDirection, IList<TextRunBounds> runBounds)
         {
             Rectangle = bounds;
             FlowDirection = flowDirection;
+            TextRunBounds = runBounds;
         }
 
         /// <summary>
         /// Bounds rectangle
         /// </summary>
-        public Rect Rectangle { get; }
+        public Rect Rectangle { get; internal set; }
 
         /// <summary>
         /// Text flow direction inside the boundary rectangle
         /// </summary>
         public FlowDirection FlowDirection { get; }
+
+        /// <summary>
+        /// Get a list of run bounding rectangles
+        /// </summary>
+        /// <returns>Array of text run bounds</returns>
+        public IList<TextRunBounds> TextRunBounds { get; }
     }
 }

+ 19 - 10
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -230,7 +230,7 @@ namespace Avalonia.Media.TextFormatting
             foreach (var textLine in TextLines)
             {
                 //Current line isn't covered.
-                if (textLine.FirstTextSourceIndex + textLine.Length <= start)
+                if (textLine.FirstTextSourceIndex + textLine.Length < start)
                 {
                     currentY += textLine.Height;
 
@@ -239,18 +239,27 @@ namespace Avalonia.Media.TextFormatting
 
                 var textBounds = textLine.GetTextBounds(start, length);
 
-                foreach (var bounds in textBounds)
+                if(textBounds.Count > 0)
                 {
-                    Rect? last = result.Count > 0 ? result[result.Count - 1] : null;
-
-                    if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY))
+                    foreach (var bounds in textBounds)
                     {
-                        result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width);
+                        Rect? last = result.Count > 0 ? result[result.Count - 1] : null;
+
+                        if (last.HasValue && MathUtilities.AreClose(last.Value.Right, bounds.Rectangle.Left) && MathUtilities.AreClose(last.Value.Top, currentY))
+                        {
+                            result[result.Count - 1] = last.Value.WithWidth(last.Value.Width + bounds.Rectangle.Width);
+                        }
+                        else
+                        {
+                            result.Add(bounds.Rectangle.WithY(currentY));
+                        }
+
+                        foreach (var runBounds in bounds.TextRunBounds)
+                        {
+                            start += runBounds.Length;
+                            length -= runBounds.Length;
+                        }
                     }
-                    else
-                    {
-                        result.Add(bounds.Rectangle.WithY(currentY));
-                    }                  
                 }
 
                 if(textLine.FirstTextSourceIndex + textLine.Length >= start + length)

+ 241 - 135
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -184,6 +184,10 @@ namespace Avalonia.Media.TextFormatting
                         {
                             characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
 
+                            var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
+
+                            characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
+
                             break;
                         }
                     default:
@@ -215,9 +219,11 @@ namespace Avalonia.Media.TextFormatting
         /// <inheritdoc/>
         public override double GetDistanceFromCharacterHit(CharacterHit characterHit)
         {
-            var characterIndex = characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0);
+            var isTrailingHit = characterHit.TrailingLength > 0;
+            var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
             var currentDistance = Start;
             var currentPosition = FirstTextSourceIndex;
+            var remainingLength = characterIndex - FirstTextSourceIndex;
 
             GlyphRun? lastRun = null;
 
@@ -242,8 +248,10 @@ namespace Avalonia.Media.TextFormatting
                             }
 
                             //Look for a hit in within the current run
-                            if (characterIndex >= textRun.Text.Start && characterIndex <= textRun.Text.Start + textRun.Text.Length)
+                            if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length)
                             {
+                                characterHit = new CharacterHit(textRun.Text.Start + remainingLength);
+
                                 var distance = currentRun.GetDistanceFromCharacterHit(characterHit);
 
                                 return currentDistance + distance;
@@ -254,28 +262,27 @@ namespace Avalonia.Media.TextFormatting
                             {
                                 if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
                                 {
-                                    if (characterIndex <= textRun.Text.Start)
+                                    if (characterIndex <= currentPosition)
                                     {
                                         return currentDistance;
                                     }
                                 }
                                 else
                                 {
-                                    if (characterIndex == textRun.Text.Start)
+                                    if (characterIndex == currentPosition)
                                     {
                                         return currentDistance;
                                     }
                                 }
 
-                                if (characterIndex == textRun.Text.Start + textRun.Text.Length &&
-                                    characterHit.TrailingLength > 0)
+                                if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit)
                                 {
                                     return currentDistance + currentRun.Size.Width;
                                 }
                             }
                             else
                             {
-                                if (characterIndex == textRun.Text.Start)
+                                if (characterIndex == currentPosition)
                                 {
                                     return currentDistance + currentRun.Size.Width;
                                 }
@@ -286,20 +293,24 @@ namespace Avalonia.Media.TextFormatting
 
                                 if (nextRun != null)
                                 {
-                                    if (characterHit.FirstCharacterIndex == textRun.Text.End &&
-                                        nextRun.ShapedBuffer.IsLeftToRight)
+                                    if (nextRun.ShapedBuffer.IsLeftToRight)
                                     {
-                                        return currentDistance;
+                                        if (characterIndex == currentPosition + textRun.Text.Length)
+                                        {
+                                            return currentDistance;
+                                        }
                                     }
-
-                                    if (characterIndex > textRun.Text.End && nextRun.Text.End < textRun.Text.End)
+                                    else
                                     {
-                                        return currentDistance;
+                                        if (currentPosition + nextRun.Text.Length == characterIndex)
+                                        {
+                                            return currentDistance;
+                                        }
                                     }
                                 }
                                 else
                                 {
-                                    if (characterIndex > textRun.Text.End)
+                                    if (characterIndex > currentPosition + textRun.Text.Length)
                                     {
                                         return currentDistance;
                                     }
@@ -329,6 +340,12 @@ namespace Avalonia.Media.TextFormatting
                 //No hit hit found so we add the full width
                 currentDistance += textRun.Size.Width;
                 currentPosition += textRun.TextSourceLength;
+                remainingLength -= textRun.TextSourceLength;
+
+                if (remainingLength <= 0)
+                {
+                    break;
+                }
             }
 
             return currentDistance;
@@ -394,210 +411,299 @@ namespace Avalonia.Media.TextFormatting
             return GetPreviousCaretCharacterHit(characterHit);
         }
 
-        public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceCharacterIndex, int textLength)
+        private IReadOnlyList<TextBounds> GetTextBoundsLeftToRight(int firstTextSourceIndex, int textLength)
         {
-            if (firstTextSourceCharacterIndex + textLength <= FirstTextSourceIndex)
-            {
-                return Array.Empty<TextBounds>();
-            }
+            var characterIndex = firstTextSourceIndex + textLength;
 
             var result = new List<TextBounds>(TextRuns.Count);
-            var lastDirection = _flowDirection;
+            var lastDirection = FlowDirection.LeftToRight;
             var currentDirection = lastDirection;
+
             var currentPosition = FirstTextSourceIndex;
-            var currentRect = Rect.Empty;
+            var remainingLength = textLength;
+
             var startX = Start;
+            double currentWidth = 0;
+            var currentRect = Rect.Empty;
 
-            //A portion of the line is covered.
             for (var index = 0; index < TextRuns.Count; index++)
             {
-                var currentRun = TextRuns[index] as DrawableTextRun;
-
-                if (currentRun is null)
+                if (TextRuns[index] is not DrawableTextRun currentRun)
                 {
                     continue;
                 }
 
-                TextRun? nextRun = null;
-
-                if (index + 1 < TextRuns.Count)
+                if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
                 {
-                    nextRun = TextRuns[index + 1];
+                    startX += currentRun.Size.Width;
+
+                    currentPosition += currentRun.TextSourceLength;
+
+                    continue;
                 }
 
-                if (nextRun != null)
+                var characterLength = 0;
+                var endX = startX;
+
+                if (currentRun is ShapedTextCharacters currentShapedRun)
                 {
-                    switch (nextRun)
-                    {
-                        case ShapedTextCharacters when currentRun is ShapedTextCharacters:
-                            {
-                                if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End)
-                                {
-                                    goto skip;
-                                }
+                    var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
 
-                                if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength)
-                                {
-                                    goto skip;
-                                }
+                    currentPosition += offset;
 
-                                if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex)
-                                {
-                                    goto skip;
-                                }
+                    var startIndex = currentRun.Text.Start + offset;
 
-                                if (currentRun.Text.End < firstTextSourceCharacterIndex)
-                                {
-                                    goto skip;
-                                }
+                    var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+                       currentShapedRun.ShapedBuffer.IsLeftToRight ?
+                            new CharacterHit(startIndex + remainingLength) :
+                            new CharacterHit(startIndex));
 
-                                goto noop;
-                            }
-                        default:
-                            {
-                                goto noop;
-                            }
-                    }
+                    endX += endOffset;
+
+                    var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+                        currentShapedRun.ShapedBuffer.IsLeftToRight ?
+                            new CharacterHit(startIndex) :
+                            new CharacterHit(startIndex + remainingLength));
+
+                    startX += startOffset;
+
+                    var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+                    var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
+
+                    characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength);
 
-                skip:
+                    currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ?
+                        FlowDirection.LeftToRight :
+                        FlowDirection.RightToLeft;
+                }
+                else
+                {
+                    if (currentPosition < firstTextSourceIndex)
                     {
                         startX += currentRun.Size.Width;
-                        currentPosition += currentRun.TextSourceLength;
                     }
 
-                    continue;
-
-                noop:
+                    if (currentPosition + currentRun.TextSourceLength <= characterIndex)
                     {
+                        endX += currentRun.Size.Width;
+
+                        characterLength = currentRun.TextSourceLength;
                     }
                 }
 
-                var endX = startX;
-                var endOffset = 0d;
+                if (endX < startX)
+                {
+                    (endX, startX) = (startX, endX);
+                }
 
-                switch (currentRun)
+                //Lines that only contain a linebreak need to be covered here
+                if(characterLength == 0)
                 {
-                    case ShapedTextCharacters shapedRun:
-                        {
-                            endOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit(
-                                shapedRun.ShapedBuffer.IsLeftToRight ?
-                                    new CharacterHit(firstTextSourceCharacterIndex + textLength) :
-                                    new CharacterHit(firstTextSourceCharacterIndex));
+                    characterLength = NewLineLength;
+                }
 
-                            endX += endOffset;
+                var runwidth = endX - startX;
+                var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runwidth, Height), currentPosition, characterLength, currentRun);
 
-                            var startOffset = shapedRun.GlyphRun.GetDistanceFromCharacterHit(
-                                shapedRun.ShapedBuffer.IsLeftToRight ?
-                                    new CharacterHit(firstTextSourceCharacterIndex) :
-                                    new CharacterHit(firstTextSourceCharacterIndex + textLength));
+                if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+                {
+                    currentRect = currentRect.WithWidth(currentWidth + runwidth);
 
-                            startX += startOffset;
+                    var textBounds = result[result.Count - 1];
 
-                            var characterHit = shapedRun.GlyphRun.IsLeftToRight ?
-                                shapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _) :
-                                shapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
+                    textBounds.Rectangle = currentRect;
 
-                            currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
+                    textBounds.TextRunBounds.Add(currentRunBounds);
+                }
+                else
+                {
+                    currentRect = currentRunBounds.Rectangle;
 
-                            currentDirection = shapedRun.ShapedBuffer.IsLeftToRight ?
-                                FlowDirection.LeftToRight :
-                                FlowDirection.RightToLeft;
+                    result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
+                }
 
-                            if (nextRun is ShapedTextCharacters nextShaped)
-                            {
-                                if (shapedRun.ShapedBuffer.IsLeftToRight == nextShaped.ShapedBuffer.IsLeftToRight)
-                                {
-                                    endOffset = nextShaped.GlyphRun.GetDistanceFromCharacterHit(
-                                        nextShaped.ShapedBuffer.IsLeftToRight ?
-                                            new CharacterHit(firstTextSourceCharacterIndex + textLength) :
-                                            new CharacterHit(firstTextSourceCharacterIndex));
+                currentWidth += runwidth;
+                currentPosition += characterLength;
 
-                                    index++;
+                if (currentDirection == FlowDirection.LeftToRight)
+                {
+                    if (currentPosition > characterIndex)
+                    {
+                        break;
+                    }
+                }
+                else
+                {
+                    if (currentPosition <= firstTextSourceIndex)
+                    {
+                        break;
+                    }
+                }
 
-                                    endX += endOffset;
+                startX = endX;
+                lastDirection = currentDirection;
+                remainingLength -= characterLength;
 
-                                    currentRun = nextShaped;
+                if (remainingLength <= 0)
+                {
+                    break;
+                }
+            }
 
-                                    if (nextShaped.ShapedBuffer.IsLeftToRight)
-                                    {
-                                        characterHit = nextShaped.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+            return result;
+        }
 
-                                        currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
-                                    }
-                                }
-                            }
+        private IReadOnlyList<TextBounds> GetTextBoundsRightToLeft(int firstTextSourceIndex, int textLength)
+        {
+            var characterIndex = firstTextSourceIndex + textLength;
 
-                            break;
-                        }
-                    default:
-                        {
-                            if (currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex + textLength)
-                            {
-                                endX += currentRun.Size.Width;
-                            }
+            var result = new List<TextBounds>(TextRuns.Count);
+            var lastDirection = FlowDirection.LeftToRight;
+            var currentDirection = lastDirection;
 
-                            if (currentPosition < firstTextSourceCharacterIndex)
-                            {
-                                startX += currentRun.Size.Width;
-                            }
+            var currentPosition = FirstTextSourceIndex;
+            var remainingLength = textLength;
 
-                            currentPosition += currentRun.TextSourceLength;
+            var startX = Start + WidthIncludingTrailingWhitespace;
+            double currentWidth = 0;
+            var currentRect = Rect.Empty;
 
-                            break;
-                        }
+            for (var index = TextRuns.Count - 1; index >= 0; index--)
+            {
+                if (TextRuns[index] is not DrawableTextRun currentRun)
+                {
+                    continue;
                 }
 
-                if (endX < startX)
+                if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
                 {
-                    (endX, startX) = (startX, endX);
+                    startX -= currentRun.Size.Width;
+
+                    currentPosition += currentRun.TextSourceLength;
+
+                    continue;
                 }
 
-                var width = endX - startX;
+                var characterLength = 0;
+                var endX = startX;
 
-                if (!MathUtilities.IsZero(width))
+                if (currentRun is ShapedTextCharacters currentShapedRun)
                 {
-                    if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
-                    {
-                        currentRect = currentRect.WithWidth(currentRect.Width + width);
+                    var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
+
+                    currentPosition += offset;
+
+                    var startIndex = currentRun.Text.Start + offset;
+
+                    var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+                        currentShapedRun.ShapedBuffer.IsLeftToRight ?
+                            new CharacterHit(startIndex + remainingLength) :
+                            new CharacterHit(startIndex));
+
+                    endX += endOffset - currentShapedRun.Size.Width;
+
+                    var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(
+                        currentShapedRun.ShapedBuffer.IsLeftToRight ?
+                            new CharacterHit(startIndex) :
+                            new CharacterHit(startIndex + remainingLength));
 
-                        var textBounds = new TextBounds(currentRect, currentDirection);
+                    startX += startOffset - currentShapedRun.Size.Width;
 
-                        result[result.Count - 1] = textBounds;
+                    var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+                    var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
+
+                    characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
+
+                    currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ?
+                        FlowDirection.LeftToRight :
+                        FlowDirection.RightToLeft;
+                }
+                else
+                {
+                    if (currentPosition + currentRun.TextSourceLength <= characterIndex)
+                    {
+                        endX -= currentRun.Size.Width;
                     }
-                    else
+
+                    if (currentPosition < firstTextSourceIndex)
                     {
+                        startX -= currentRun.Size.Width;
+
+                        characterLength = currentRun.TextSourceLength;
+                    }
+                }
+
+                if (endX < startX)
+                {
+                    (endX, startX) = (startX, endX);
+                }
 
-                        currentRect = new Rect(startX, 0, width, Height);
+                //Lines that only contain a linebreak need to be covered here
+                if (characterLength == 0)
+                {
+                    characterLength = NewLineLength;
+                }
 
-                        result.Add(new TextBounds(currentRect, currentDirection));
+                var runWidth = endX - startX;
+                var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
 
-                    }
+                if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+                {
+                    currentRect = currentRect.WithWidth(currentWidth + runWidth);
+
+                    var textBounds = result[result.Count - 1];
+
+                    textBounds.Rectangle = currentRect;
+
+                    textBounds.TextRunBounds.Add(currentRunBounds);
+                }
+                else
+                {
+                    currentRect = currentRunBounds.Rectangle;
+
+                    result.Add(new TextBounds(currentRect, currentDirection, new List<TextRunBounds> { currentRunBounds }));
                 }
 
+                currentWidth += runWidth;
+                currentPosition += characterLength;
+
                 if (currentDirection == FlowDirection.LeftToRight)
                 {
-                    if (currentPosition > firstTextSourceCharacterIndex + textLength)
+                    if (currentPosition > characterIndex)
                     {
                         break;
                     }
                 }
                 else
                 {
-                    if (currentPosition <= firstTextSourceCharacterIndex)
+                    if (currentPosition <= firstTextSourceIndex)
                     {
                         break;
                     }
-
-                    endX += currentRun.Size.Width - endOffset;
                 }
 
                 lastDirection = currentDirection;
-                startX = endX;
+                remainingLength -= characterLength;
+
+                if (remainingLength <= 0)
+                {
+                    break;
+                }
             }
 
             return result;
         }
 
+        public override IReadOnlyList<TextBounds> GetTextBounds(int firstTextSourceIndex, int textLength)
+        {
+            if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight)
+            {
+                return GetTextBoundsLeftToRight(firstTextSourceIndex, textLength);
+            }
+
+            return GetTextBoundsRightToLeft(firstTextSourceIndex, textLength);
+        }
+
         public TextLineImpl FinalizeLine()
         {
             _textLineMetrics = CreateLineMetrics();

+ 39 - 0
src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs

@@ -0,0 +1,39 @@
+namespace Avalonia.Media.TextFormatting
+{
+    /// <summary>
+    /// The bounding rectangle of text run
+    /// </summary>
+    public sealed class TextRunBounds
+    {
+        /// <summary>
+        /// Constructing TextRunBounds
+        /// </summary>
+        internal TextRunBounds(Rect bounds, int firstCharacterIndex, int length, TextRun textRun)
+        {
+            Rectangle = bounds;
+            TextSourceCharacterIndex = firstCharacterIndex;
+            Length = length;
+            TextRun = textRun;
+        }
+
+        /// <summary>
+        /// First text source character index of text run
+        /// </summary>
+        public int TextSourceCharacterIndex { get; }
+
+        /// <summary>
+        /// character length of bounded text run
+        /// </summary>
+        public int Length { get; }
+
+        /// <summary>
+        /// Text run bounding rectangle
+        /// </summary>
+        public Rect Rectangle { get; }
+
+        /// <summary>
+        /// text run
+        /// </summary>
+        public TextRun TextRun { get; }
+    }
+}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません