浏览代码

Merge pull request #1 from AvaloniaUI/master

get latest master
Splitwirez 5 年之前
父节点
当前提交
ec818b3e5a
共有 100 个文件被更改,包括 3397 次插入452 次删除
  1. 2 2
      .gitmodules
  2. 90 1
      Avalonia.sln
  3. 1 0
      Avalonia.v3.ncrunchsolution
  4. 1 0
      Directory.Build.props
  5. 2 2
      build/HarfBuzzSharp.props
  6. 2 2
      build/SkiaSharp.props
  7. 1 1
      dirs.proj
  8. 13 0
      native/Avalonia.Native/inc/avalonia-native.h
  9. 12 4
      native/Avalonia.Native/src/OSX/Screens.mm
  10. 10 3
      native/Avalonia.Native/src/OSX/window.h
  11. 273 29
      native/Avalonia.Native/src/OSX/window.mm
  12. 2 0
      nukebuild/Build.cs
  13. 76 0
      nukebuild/BuildTasksPatcher.cs
  14. 3 0
      nukebuild/_build.csproj
  15. 1 1
      readme.md
  16. 3 2
      samples/BindingDemo/MainWindow.xaml
  17. 2 2
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  18. 2 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  19. 54 1
      samples/ControlCatalog.NetCore/Program.cs
  20. 23 15
      samples/ControlCatalog/App.xaml.cs
  21. 4 2
      samples/ControlCatalog/MainView.xaml
  22. 0 7
      samples/ControlCatalog/MainView.xaml.cs
  23. 28 13
      samples/ControlCatalog/MainWindow.xaml
  24. 139 0
      samples/ControlCatalog/Pages/AcrylicPage.xaml
  25. 19 0
      samples/ControlCatalog/Pages/AcrylicPage.xaml.cs
  26. 6 6
      samples/ControlCatalog/Pages/BorderPage.xaml
  27. 7 7
      samples/ControlCatalog/Pages/ButtonPage.xaml
  28. 1 1
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  29. 2 2
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  30. 3 3
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  31. 26 10
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  32. 26 0
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  33. 4 4
      samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
  34. 7 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  35. 60 0
      samples/ControlCatalog/Pages/RelativePanelPage.axaml
  36. 19 0
      samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
  37. 30 39
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  38. 2 2
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  39. 19 0
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  40. 19 0
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs
  41. 3 3
      samples/ControlCatalog/SideBar.xaml
  42. 2 8
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  43. 65 0
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  44. 2 3
      samples/RenderDemo/App.xaml
  45. 2 2
      samples/RenderDemo/Pages/ClippingPage.xaml
  46. 64 61
      samples/RenderDemo/SideBar.xaml
  47. 1 0
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  48. 1 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  49. 7 1
      src/Avalonia.Base/AvaloniaProperty.cs
  50. 73 0
      src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
  51. 23 9
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  52. 14 0
      src/Avalonia.Base/Data/Core/IPropertyInfo.cs
  53. 1 1
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  54. 21 11
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  55. 91 0
      src/Avalonia.Base/Data/Core/PropertyPath.cs
  56. 28 9
      src/Avalonia.Base/Data/Core/StreamNode.cs
  57. 26 1
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  58. 20 0
      src/Avalonia.Base/Utilities/CharacterReader.cs
  59. 46 0
      src/Avalonia.Base/Utilities/KeywordParser.cs
  60. 19 27
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  61. 1 1
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  62. 35 25
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  63. 8 0
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  64. 5 0
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  65. 5 0
      src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
  66. 1 1
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  67. 23 0
      src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs
  68. 84 0
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  69. 97 0
      src/Avalonia.Controls/Chrome/TitleBar.cs
  70. 31 0
      src/Avalonia.Controls/ComboBox.cs
  71. 10 6
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  72. 17 14
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  73. 18 33
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  74. 18 20
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  75. 4 1
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  76. 117 0
      src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
  77. 0 1
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  78. 38 0
      src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
  79. 8 3
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  80. 5 0
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  81. 46 0
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  82. 5 0
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  83. 11 11
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  84. 0 2
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  85. 2 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  86. 10 11
      src/Avalonia.Controls/Primitives/AccessText.cs
  87. 34 0
      src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
  88. 2 2
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  89. 15 5
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  90. 9 0
      src/Avalonia.Controls/Primitives/Track.cs
  91. 31 5
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  92. 1 0
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  93. 546 0
      src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
  94. 536 0
      src/Avalonia.Controls/RelativePanel.cs
  95. 1 1
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  96. 27 0
      src/Avalonia.Controls/Repeater/ElementFactory.cs
  97. 66 0
      src/Avalonia.Controls/Repeater/IElementFactory.cs
  98. 16 3
      src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
  99. 2 2
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  100. 9 3
      src/Avalonia.Controls/Repeater/RecyclePool.cs

+ 2 - 2
.gitmodules

@@ -2,5 +2,5 @@
 	path = nukebuild/Numerge
 	url = https://github.com/kekekeks/Numerge.git
 [submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
-	path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
-	url = https://github.com/kekekeks/XamlIl.git
+	path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
+	url = https://github.com/kekekeks/XamlX.git

+ 90 - 1
Avalonia.sln

@@ -123,10 +123,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
 	ProjectSection(SolutionItems) = preProject
+		build\AndroidWorkarounds.props = build\AndroidWorkarounds.props
 		build\Base.props = build\Base.props
 		build\Binding.props = build\Binding.props
-		build\BuildTargets.targets = build\BuildTargets.targets
+		build\CoreLibraries.props = build\CoreLibraries.props
+		build\EmbedXaml.props = build\EmbedXaml.props
 		build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
+		build\iOSWorkarounds.props = build\iOSWorkarounds.props
 		build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
 		build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
 		build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@@ -136,17 +139,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\NetCore.props = build\NetCore.props
 		build\NetFX.props = build\NetFX.props
 		build\ReactiveUI.props = build\ReactiveUI.props
+		build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
 		build\Rx.props = build\Rx.props
 		build\SampleApp.props = build\SampleApp.props
+		build\SharedVersion.props = build\SharedVersion.props
 		build\SharpDX.props = build\SharpDX.props
 		build\SkiaSharp.props = build\SkiaSharp.props
+		build\SourceLink.props = build\SourceLink.props
+		build\System.Drawing.Common.props = build\System.Drawing.Common.props
 		build\System.Memory.props = build\System.Memory.props
+		build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
 		build\XUnit.props = build\XUnit.props
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
 	ProjectSection(SolutionItems) = preProject
 		build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
+		build\BuildTargets.targets = build\BuildTargets.targets
+		build\LegacyProject.targets = build\LegacyProject.targets
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}"
@@ -207,6 +217,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "sample
 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1826,6 +1842,54 @@ Global
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhone.Build.0 = Release|Any CPU
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhone.Build.0 = Release|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhone.Build.0 = Release|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
 		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
 		{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@@ -1946,6 +2010,30 @@ Global
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.Build.0 = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.Build.0 = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2004,6 +2092,7 @@ Global
 		{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}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 1 - 0
Avalonia.v3.ncrunchsolution

@@ -3,6 +3,7 @@
     <AdditionalFilesToIncludeForSolution>
       <Value>tests\TestFiles\**.*</Value>
       <Value>src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll</Value>
+      <Value>src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.dll</Value>
     </AdditionalFilesToIncludeForSolution>
     <AllowParallelTestExecution>True</AllowParallelTestExecution>
     <ProjectConfigStoragePathRelativeToSolutionDir>.ncrunch</ProjectConfigStoragePathRelativeToSolutionDir>

+ 1 - 0
Directory.Build.props

@@ -1,5 +1,6 @@
 <Project>
   <PropertyGroup>
       <PackageOutputPath Condition="'$(PackageOutputPath)' == ''">$(MSBuildThisFileDirectory)build-intermediate/nuget</PackageOutputPath>
+      <AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
   </PropertyGroup>
 </Project>

+ 2 - 2
build/HarfBuzzSharp.props

@@ -1,6 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="HarfBuzzSharp" Version="2.6.1" />
-    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1" />
+    <PackageReference Include="HarfBuzzSharp" Version="2.6.1.5" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1.5" />
   </ItemGroup>
 </Project>

+ 2 - 2
build/SkiaSharp.props

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

+ 1 - 1
dirs.proj

@@ -6,7 +6,7 @@
     <ProjectReference Include="packages/**/*.*proj" />
     <ProjectReference Remove="**/*.shproj" />
     <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
-    <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
+    <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" />
   </ItemGroup>
   <!--<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">-->
   <ItemGroup>

+ 13 - 0
native/Avalonia.Native/inc/avalonia-native.h

@@ -205,6 +205,15 @@ enum AvnMenuItemToggleType
     Radio
 };
 
+enum AvnExtendClientAreaChromeHints
+{
+    AvnNoChrome = 0,
+    AvnSystemChrome = 0x01,
+    AvnPreferSystemChrome = 0x02,
+    AvnOSXThickTitleBar = 0x08,
+    AvnDefaultChrome = AvnSystemChrome,
+};
+
 AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
 {
 public:
@@ -279,6 +288,10 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
     virtual HRESULT SetWindowState(AvnWindowState state) = 0;
     virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
     virtual HRESULT TakeFocusFromChildren() = 0;
+    virtual HRESULT SetExtendClientArea (bool enable) = 0;
+    virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) = 0;
+    virtual HRESULT GetExtendTitleBarHeight (double*ret) = 0;
+    virtual HRESULT SetExtendTitleBarHeight (double value) = 0;
 };
 
 AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown

+ 12 - 4
native/Avalonia.Native/src/OSX/Screens.mm

@@ -4,6 +4,14 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
 {
     public:
     FORWARD_IUNKNOWN()
+    
+    private:
+    CGFloat PrimaryDisplayHeight()
+    {
+      return NSMaxY([[[NSScreen screens] firstObject] frame]);
+    }
+    
+public:
     virtual HRESULT GetScreenCount (int* ret) override
     {
         @autoreleasepool
@@ -25,15 +33,15 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
             
             auto screen = [[NSScreen screens] objectAtIndex:index];
             
-            ret->Bounds.X = [screen frame].origin.x;
-            ret->Bounds.Y = [screen frame].origin.y;
             ret->Bounds.Height = [screen frame].size.height;
             ret->Bounds.Width = [screen frame].size.width;
+            ret->Bounds.X = [screen frame].origin.x;
+            ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
             
-            ret->WorkingArea.X = [screen visibleFrame].origin.x;
-            ret->WorkingArea.Y = [screen visibleFrame].origin.y;
             ret->WorkingArea.Height = [screen visibleFrame].size.height;
             ret->WorkingArea.Width = [screen visibleFrame].size.width;
+            ret->WorkingArea.X = [screen visibleFrame].origin.x;
+            ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
             
             ret->PixelDensity = [screen backingScaleFactor];
             

+ 10 - 3
native/Avalonia.Native/src/OSX/window.h

@@ -3,9 +3,6 @@
 
 class WindowBaseImpl;
 
-@interface AutoFitContentVisualEffectView : NSVisualEffectView
-@end
-
 @interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
 -(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
 -(NSEvent* _Nonnull) lastMouseDownEvent;
@@ -15,6 +12,14 @@ class WindowBaseImpl;
 -(AvnPixelSize) getPixelSize;
 @end
 
+@interface AutoFitContentView : NSView
+-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
+-(void) ShowTitleBar: (bool) show;
+-(void) SetTitleBarHeightHint: (double) height;
+-(void) SetContent: (NSView* _Nonnull) content;
+-(void) ShowBlur: (bool) show;
+@end
+
 @interface AvnWindow : NSWindow <NSWindowDelegate>
 +(void) closeAll;
 -(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
@@ -27,6 +32,8 @@ class WindowBaseImpl;
 -(void) showWindowMenuWithAppMenu;
 -(void) applyMenu:(NSMenu* _Nullable)menu;
 -(double) getScaling;
+-(double) getExtendedTitleBarHeight;
+-(void) setIsExtended:(bool)value;
 @end
 
 struct INSWindowHolder

+ 273 - 29
native/Avalonia.Native/src/OSX/window.mm

@@ -6,8 +6,6 @@
 #include <OpenGL/gl.h>
 #include "rendertarget.h"
 
-
-
 class WindowBaseImpl : public virtual ComSingleObject<IAvnWindowBase, &IID_IAvnWindowBase>, public INSWindowHolder
 {
 private:
@@ -20,7 +18,7 @@ public:
         View = NULL;
         Window = NULL;
     }
-    NSVisualEffectView* VisualEffect;
+    AutoFitContentView* StandardContainer;
     AvnView* View;
     AvnWindow* Window;
     ComPtr<IAvnWindowBaseEvents> BaseEvents;
@@ -39,6 +37,7 @@ public:
         _glContext = gl;
         renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl];
         View = [[AvnView alloc] initWithParent:this];
+        StandardContainer = [[AutoFitContentView new] initWithContent:View];
 
         Window = [[AvnWindow alloc] initWithParent:this];
         
@@ -49,12 +48,8 @@ public:
         [Window setStyleMask:NSWindowStyleMaskBorderless];
         [Window setBackingType:NSBackingStoreBuffered];
         
-        VisualEffect = [AutoFitContentVisualEffectView new];
-        [VisualEffect setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
-        [VisualEffect setMaterial:NSVisualEffectMaterialLight];
-        [VisualEffect setAutoresizesSubviews:true];
-        
-        [Window setContentView: View];
+        [Window setOpaque:false];
+        [Window setContentView: StandardContainer];
     }
     
     virtual HRESULT ObtainNSWindowHandle(void** ret) override
@@ -410,12 +405,7 @@ public:
     
     virtual HRESULT SetBlurEnabled (bool enable) override
     {
-        [Window setContentView: enable ? VisualEffect : View];
-        
-        if(enable)
-        {
-            [VisualEffect addSubview:View];
-        }
+        [StandardContainer ShowBlur:enable];
         
         return S_OK;
     }
@@ -492,6 +482,8 @@ private:
     bool _inSetWindowState;
     NSRect _preZoomSize;
     bool _transitioningWindowState;
+    bool _isClientAreaExtended;
+    AvnExtendClientAreaChromeHints _extendClientHints;
     
     FORWARD_IUNKNOWN()
     BEGIN_INTERFACE_MAP()
@@ -505,6 +497,8 @@ private:
     ComPtr<IAvnWindowEvents> WindowEvents;
     WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
     {
+        _isClientAreaExtended = false;
+        _extendClientHints = AvnDefaultChrome;
         _fullScreenActive = false;
         _canResize = true;
         _decorations = SystemDecorationsFull;
@@ -523,8 +517,20 @@ private:
             if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
                 NSView *titlebarView = [subview subviews][0];
                 for (id button in titlebarView.subviews) {
-                    if ([button isKindOfClass:[NSButton class]]) {
-                        [button setHidden: (_decorations != SystemDecorationsFull)];
+                    if ([button isKindOfClass:[NSButton class]])
+                    {
+                        if(_isClientAreaExtended)
+                        {
+                            auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+                            
+                            [button setHidden: !wantsChrome];
+                        }
+                        else
+                        {
+                            [button setHidden: (_decorations != SystemDecorationsFull)];
+                        }
+                        
+                        [button setWantsLayer:true];
                     }
                 }
             }
@@ -600,6 +606,35 @@ private:
             
             if(_lastWindowState != state)
             {
+                if(_isClientAreaExtended)
+                {
+                    if(_lastWindowState == FullScreen)
+                    {
+                        // we exited fs.
+                       if(_extendClientHints & AvnOSXThickTitleBar)
+                       {
+                          Window.toolbar = [NSToolbar new];
+                          Window.toolbar.showsBaselineSeparator = false;
+                       }
+
+                       [Window setTitlebarAppearsTransparent:true];
+
+                       [StandardContainer setFrameSize: StandardContainer.frame.size];
+                    }
+                    else if(state == FullScreen)
+                    {
+                        // we entered fs.
+                        if(_extendClientHints & AvnOSXThickTitleBar)
+                        {
+                            Window.toolbar = nullptr;
+                        }
+                       
+                        [Window setTitlebarAppearsTransparent:false];
+                        
+                        [StandardContainer setFrameSize: StandardContainer.frame.size];
+                    }
+                }
+                
                 _lastWindowState = state;
                 WindowEvents->WindowStateChanged(state);
             }
@@ -656,8 +691,6 @@ private:
                 return S_OK;
             }
             
-            auto currentFrame = [Window frame];
-            
             UpdateStyle();
             
             HideOrShowTrafficLights();
@@ -790,6 +823,81 @@ private:
             return S_OK;
         if([Window isKeyWindow])
             [Window makeFirstResponder: View];
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT SetExtendClientArea (bool enable) override
+    {
+        _isClientAreaExtended = enable;
+        
+        if(enable)
+        {
+            Window.titleVisibility = NSWindowTitleHidden;
+            
+            [Window setTitlebarAppearsTransparent:true];
+            
+            auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+            
+            if (wantsTitleBar)
+            {
+                [StandardContainer ShowTitleBar:true];
+            }
+            else
+            {
+                [StandardContainer ShowTitleBar:false];
+            }
+            
+            if(_extendClientHints & AvnOSXThickTitleBar)
+            {
+                Window.toolbar = [NSToolbar new];
+                Window.toolbar.showsBaselineSeparator = false;
+            }
+            else
+            {
+                Window.toolbar = nullptr;
+            }
+        }
+        else
+        {
+            Window.titleVisibility = NSWindowTitleVisible;
+            Window.toolbar = nullptr;
+            [Window setTitlebarAppearsTransparent:false];
+            View.layer.zPosition = 0;
+        }
+        
+        [Window setIsExtended:enable];
+        
+        HideOrShowTrafficLights();
+        
+        UpdateStyle();
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override
+    {
+        _extendClientHints = hints;
+        
+        SetExtendClientArea(_isClientAreaExtended);
+        return S_OK;
+    }
+    
+    virtual HRESULT GetExtendTitleBarHeight (double*ret) override
+    {
+        if(ret == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *ret = [Window getExtendedTitleBarHeight];
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT SetExtendTitleBarHeight (double value) override
+    {
+        [StandardContainer SetTitleBarHeightHint:value];
         return S_OK;
     }
     
@@ -802,8 +910,9 @@ private:
         [Window setTitlebarAppearsTransparent:NO];
         [Window setTitle:_lastTitle];
         
-        [Window setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable];
-        
+        Window.styleMask = Window.styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
+        Window.styleMask = Window.styleMask & ~NSWindowStyleMaskFullSizeContentView;
+    
         [Window toggleFullScreen:nullptr];
     }
     
@@ -951,19 +1060,120 @@ protected:
         {
             s |= NSWindowStyleMaskMiniaturizable;
         }
+        
+        if(_isClientAreaExtended)
+        {
+            s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
+        }
         return s;
     }
 };
 
 NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil];
 
-@implementation AutoFitContentVisualEffectView
+@implementation AutoFitContentView
+{
+    NSVisualEffectView* _titleBarMaterial;
+    NSBox* _titleBarUnderline;
+    NSView* _content;
+    NSVisualEffectView* _blurBehind;
+    double _titleBarHeightHint;
+    bool _settingSize;
+}
+
+-(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
+{
+    _titleBarHeightHint = -1;
+    _content = content;
+    _settingSize = false;
+
+    [self setAutoresizesSubviews:true];
+    [self setWantsLayer:true];
+    
+    _titleBarMaterial = [NSVisualEffectView new];
+    [_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
+    [_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
+    [_titleBarMaterial setWantsLayer:true];
+    _titleBarMaterial.hidden = true;
+    
+    _titleBarUnderline = [NSBox new];
+    _titleBarUnderline.boxType = NSBoxSeparator;
+    _titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
+    _titleBarUnderline.hidden = true;
+    
+    [self addSubview:_titleBarMaterial];
+    [self addSubview:_titleBarUnderline];
+    
+    _blurBehind = [NSVisualEffectView new];
+    [_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
+    [_blurBehind setMaterial:NSVisualEffectMaterialLight];
+    [_blurBehind setWantsLayer:true];
+    _blurBehind.hidden = true;
+    
+    [self addSubview:_blurBehind];
+    [self addSubview:_content];
+    
+    [self setWantsLayer:true];
+    return self;
+}
+
+-(void) ShowBlur:(bool)show
+{
+    _blurBehind.hidden = !show;
+}
+
+-(void) ShowTitleBar: (bool) show
+{
+    _titleBarMaterial.hidden = !show;
+    _titleBarUnderline.hidden = !show;
+}
+
+-(void) SetTitleBarHeightHint: (double) height
+{
+    _titleBarHeightHint = height;
+    
+    [self setFrameSize:self.frame.size];
+}
+
 -(void)setFrameSize:(NSSize)newSize
 {
-    [super setFrameSize:newSize];
-    if([[self subviews] count] == 0)
+    if(_settingSize)
+    {
         return;
-    [[self subviews][0] setFrameSize: newSize];
+    }
+    
+    _settingSize = true;
+    [super setFrameSize:newSize];
+    
+    [_blurBehind setFrameSize:newSize];
+    [_content setFrameSize:newSize];
+    
+    auto window = objc_cast<AvnWindow>([self window]);
+    
+    // TODO get actual titlebar size
+    
+    double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
+    
+    NSRect tbar;
+    tbar.origin.x = 0;
+    tbar.origin.y = newSize.height - height;
+    tbar.size.width = newSize.width;
+    tbar.size.height = height;
+    
+    [_titleBarMaterial setFrame:tbar];
+    tbar.size.height = height < 1 ? 0 : 1;
+    [_titleBarUnderline setFrame:tbar];
+    _settingSize = false;
+}
+
+-(void) SetContent: (NSView* _Nonnull) content
+{
+    if(content != nullptr)
+    {
+        [content removeFromSuperview];
+        [self addSubview:content];
+        _content = content;
+    }
 }
 @end
 
@@ -1081,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     _parent->UpdateCursor();
     
     auto fsize = [self convertSizeToBacking: [self frame].size];
-    _lastPixelSize.Width = (int)fsize.width;
-    _lastPixelSize.Height = (int)fsize.height;
-    [self updateRenderTarget];
-    _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
+    
+    if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
+    {
+        _lastPixelSize.Width = (int)fsize.width;
+        _lastPixelSize.Height = (int)fsize.height;
+        [self updateRenderTarget];
+    
+        _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
+    }
 }
 
 - (void)updateLayer
@@ -1523,15 +1738,43 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     bool _canBecomeKeyAndMain;
     bool _closed;
     bool _isEnabled;
+    bool _isExtended;
     AvnMenu* _menu;
     double _lastScaling;
 }
 
+-(void) setIsExtended:(bool)value;
+{
+    _isExtended = value;
+}
+
 -(double) getScaling
 {
     return _lastScaling;
 }
 
+-(double) getExtendedTitleBarHeight
+{
+    if(_isExtended)
+    {
+        for (id subview in self.contentView.superview.subviews)
+        {
+            if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
+            {
+                NSView *titlebarView = [subview subviews][0];
+
+                return (double)titlebarView.frame.size.height;
+            }
+        }
+
+        return -1;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
 +(void)closeAll
 {
     NSArray<NSWindow*>* windows = [NSArray arrayWithArray:[NSApp windows]];
@@ -1650,6 +1893,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     [self setOpaque:NO];
     [self setBackgroundColor: [NSColor clearColor]];
     [self invalidateShadow];
+    _isExtended = false;
     return self;
 }
 

+ 2 - 0
nukebuild/Build.cs

@@ -268,6 +268,8 @@ partial class Build : NukeBuild
         .DependsOn(CreateIntermediateNugetPackages)
         .Executes(() =>
         {
+            BuildTasksPatcher.PatchBuildTasksInPackage(Parameters.NugetIntermediateRoot / "Avalonia.Build.Tasks." +
+                                                       Parameters.Version + ".nupkg");
             var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config");
             EnsureCleanDirectory(Parameters.NugetRoot);
             if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,

+ 76 - 0
nukebuild/BuildTasksPatcher.cs

@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using ILRepacking;
+using Mono.Cecil;
+
+public class BuildTasksPatcher
+{
+    public static void PatchBuildTasksInPackage(string packagePath)
+    {
+        using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
+            ZipArchiveMode.Update))
+        {
+
+            foreach (var entry in archive.Entries.ToList())
+            {
+                if (entry.Name == "Avalonia.Build.Tasks.dll")
+                {
+                    var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll");
+                    var output = temp + ".output";
+                    var patched = new MemoryStream();
+                    try
+                    {
+                        entry.ExtractToFile(temp, true);
+                        var repack = new ILRepacking.ILRepack(new RepackOptions()
+                        {
+                            Internalize = true,
+                            InputAssemblies = new[]
+                            {
+                                temp, typeof(Mono.Cecil.AssemblyDefinition).Assembly.GetModules()[0]
+                                    .FullyQualifiedName
+                            },
+                            SearchDirectories = new string[0],
+                            OutputFile = output
+                        });
+                        repack.Repack();
+
+
+                        // 'hurr-durr assembly with the same name is already loaded' prevention
+                        using (var asm = AssemblyDefinition.ReadAssembly(output,
+                            new ReaderParameters { ReadWrite = true, InMemory = true, }))
+                        {
+                            asm.Name = new AssemblyNameDefinition(
+                                "Avalonia.Build.Tasks."
+                                + Guid.NewGuid().ToString().Replace("-", ""),
+                                new Version(0, 0, 0));
+                            asm.Write(patched);
+                            patched.Position = 0;
+                        }
+                    }
+                    finally
+                    {
+                        try
+                        {
+                            if (File.Exists(temp))
+                                File.Delete(temp);
+                            if (File.Exists(output))
+                                File.Delete(output);
+                        }
+                        catch
+                        {
+                            //ignore
+                        }
+                    }
+
+                    var fn = entry.FullName;
+                    entry.Delete();
+                    var newEntry = archive.CreateEntry(fn, CompressionLevel.Optimal);
+                    using (var s = newEntry.Open())
+                        patched.CopyTo(s);
+                }
+            }
+        }
+    }
+}

+ 3 - 0
nukebuild/_build.csproj

@@ -14,6 +14,9 @@
     <PackageReference Include="xunit.runner.console" Version="2.3.1" />
     <PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
     <PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
+    <PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
+    <!-- Keep in sync with Avalonia.Build.Tasks -->
+    <PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
readme.md

@@ -16,7 +16,7 @@ To see the status of some of our features, please see our [Roadmap](https://gith
 
 ## 🚀 Getting Started
 
-The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starer guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project).
+The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project).
 
 Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/
 

+ 3 - 2
samples/BindingDemo/MainWindow.xaml

@@ -5,7 +5,8 @@
         xmlns:local="clr-namespace:BindingDemo"
         Title="AvaloniaUI Bindings Test"
         Width="800"
-        Height="600">
+        Height="600"
+        x:DataType="vm:MainWindowViewModel">
   <Window.Styles>
     <Style Selector="TextBlock.h1">
       <Setter Property="FontSize" Value="18"/>
@@ -60,7 +61,7 @@
             <TextBlock FontSize="16" Text="Scheduler"/>
             <TextBox Watermark="Background Thread" Text="{Binding CurrentTime, Mode=OneWay}"/>
             <TextBlock FontSize="16" Text="Stream Operator"/>
-            <TextBox Watermark="StreamOperator" Text="{Binding CurrentTimeObservable^, Mode=OneWay}"/>
+            <TextBox Watermark="StreamOperator" Text="{CompiledBinding CurrentTimeObservable^, Mode=OneWay}"/>
           </StackPanel>
         </StackPanel>
       </StackPanel>

+ 2 - 2
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@@ -53,7 +53,7 @@ namespace BindingDemo.ViewModels
             });
 
             CurrentTimeObservable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
-                .Select(x => DateTimeOffset.Now.ToString());
+                .Select(x => DateTimeOffset.Now);
         }
 
         public ObservableCollection<TestItem> Items { get; }
@@ -90,7 +90,7 @@ namespace BindingDemo.ViewModels
             private set { this.RaiseAndSetIfChanged(ref _currentTime, value); }
         }
 
-        public IObservable<string> CurrentTimeObservable { get; }
+        public IObservable<DateTimeOffset> CurrentTimeObservable { get; }
         public ReactiveCommand<object, Unit> StringValueCommand { get; }
 
         public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();

+ 2 - 1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -7,12 +7,13 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
-    <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001"/>
+    <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />
   </ItemGroup>
 
 

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

@@ -3,9 +3,16 @@ using System.Diagnostics;
 using System.Globalization;
 using System.Linq;
 using System.Threading;
+using System.Threading.Tasks;
 using Avalonia;
-using Avalonia.Dialogs;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Headless;
+using Avalonia.LogicalTree;
+using Avalonia.Skia;
 using Avalonia.ReactiveUI;
+using Avalonia.Threading;
+using Avalonia.Dialogs;
 
 namespace ControlCatalog.NetCore
 {
@@ -40,6 +47,52 @@ namespace ControlCatalog.NetCore
                 SilenceConsole();
                 return builder.StartLinuxFbDev(args, scaling: GetScaling());
             }
+            else if (args.Contains("--vnc"))
+            {
+                return builder.StartWithHeadlessVncPlatform(null, 5901, args, ShutdownMode.OnMainWindowClose);
+            }
+            else if (args.Contains("--full-headless"))
+            {
+                return builder
+                    .UseHeadless(true)
+                    .AfterSetup(_ =>
+                    {
+                        DispatcherTimer.RunOnce(async () =>
+                        {
+                            var window = ((IClassicDesktopStyleApplicationLifetime)Application.Current.ApplicationLifetime)
+                                .MainWindow;
+                            var tc = window.GetLogicalDescendants().OfType<TabControl>().First();
+                            foreach (var page in tc.Items.Cast<TabItem>().ToList())
+                            {
+                                // Skip DatePicker because of some layout bug in grid
+                                if (page.Header.ToString() == "DatePicker")
+                                    continue;
+                                Console.WriteLine("Selecting " + page.Header);
+                                tc.SelectedItem = page;
+                                await Task.Delay(500);
+                            }
+                            Console.WriteLine("Selecting the first page");
+                            tc.SelectedItem = tc.Items.OfType<object>().First();
+                            await Task.Delay(500);
+                            Console.WriteLine("Clicked through all pages, triggering GC");
+                            for (var c = 0; c < 3; c++)
+                            {
+                                GC.Collect(2, GCCollectionMode.Forced);
+                                await Task.Delay(500);
+                            }
+
+                            void FormatMem(string metric, long bytes)
+                            {
+                                Console.WriteLine(metric + ": " + bytes / 1024 / 1024 + "MB");
+                            }
+
+                            FormatMem("GC allocated bytes", GC.GetTotalMemory(true));
+                            FormatMem("WorkingSet64", Process.GetCurrentProcess().WorkingSet64);
+
+                        }, TimeSpan.FromSeconds(1));
+                    })
+                    .StartWithClassicDesktopLifetime(args);
+            }
             else if (args.Contains("--drm"))
             {
                 SilenceConsole();

+ 23 - 15
samples/ControlCatalog/App.xaml.cs

@@ -11,37 +11,37 @@ namespace ControlCatalog
     {
         public static Styles FluentDark = new Styles
         {
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+            new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
+                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentDark.xaml")
             },
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+        };
+
+        public static Styles FluentLight = new Styles
+        {
+            new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Fluent.Accents.FluentDark.xaml?assembly=Avalonia.Themes.Fluent")
+                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentLight.xaml")
             },
         };
 
-        public static Styles FluentLight = new Styles
+        public static Styles DefaultLight = new Styles
         {
             new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
+                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
             },
             new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Fluent.Accents.FluentLight.xaml?assembly=Avalonia.Themes.Fluent")
+                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
             },
-        };
-
-        public static Styles DefaultLight = new Styles
-        {
             new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
+                Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml")
             },
             new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default")
+                Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
             },
         };
 
@@ -49,11 +49,19 @@ namespace ControlCatalog
         {
             new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
+                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
+            },
+            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+            {
+                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
+            },
+            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+            {
+                Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml")
             },
             new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
             {
-                Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default")
+                Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
             },
         };
 

+ 4 - 2
samples/ControlCatalog/MainView.xaml

@@ -2,7 +2,6 @@
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="ControlCatalog.MainView"
-        Background="Transparent"
         Foreground="{DynamicResource ThemeForegroundBrush}"
         FontSize="{DynamicResource FontSizeNormal}">
   <Grid>
@@ -14,6 +13,7 @@
         </Style>
     </Grid.Styles>  
     <TabControl Classes="sidebar" Name="Sidebar">
+      <TabItem Header="Acrylic"><pages:AcrylicPage/></TabItem>
       <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
       <TabItem Header="Border"><pages:BorderPage/></TabItem>
       <TabItem Header="Button"><pages:ButtonPage/></TabItem>
@@ -55,6 +55,7 @@
       <TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
       <TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
       <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
+      <TabItem Header="RelativePanel"><pages:RelativePanelPage/></TabItem>
       <TabItem Header="ScrollViewer"><pages:ScrollViewerPage/></TabItem>
       <TabItem Header="Slider"><pages:SliderPage/></TabItem>
       <TabItem Header="SplitView"><pages:SplitViewPage/></TabItem>
@@ -66,6 +67,7 @@
       <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
       <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
       <TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
+      <TabItem Header="Window Customizations"><pages:WindowCustomizationsPage/></TabItem>
       <TabControl.Tag>
         <StackPanel Width="115" Spacing="4" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="8">
           <ComboBox x:Name="Decorations" SelectedIndex="0">
@@ -79,7 +81,7 @@
             <ComboBoxItem>Simple - Light</ComboBoxItem>
             <ComboBoxItem>Simple - Dark</ComboBoxItem>
           </ComboBox>
-          <ComboBox x:Name="TransparencyLevels" SelectedIndex="0">
+          <ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
             <ComboBoxItem>None</ComboBoxItem>
             <ComboBoxItem>Transparent</ComboBoxItem>
             <ComboBoxItem>Blur</ComboBoxItem>

+ 0 - 7
samples/ControlCatalog/MainView.xaml.cs

@@ -58,13 +58,6 @@ namespace ControlCatalog
                 if (VisualRoot is Window window)
                     window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex;
             };
-
-            var transparencyLevels = this.Find<ComboBox>("TransparencyLevels");
-            transparencyLevels.SelectionChanged += (sender, e) =>
-            {
-                if (VisualRoot is Window window)
-                    window.TransparencyLevelHint = (WindowTransparencyLevel)transparencyLevels.SelectedIndex;
-            };
         }
 
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)

+ 28 - 13
samples/ControlCatalog/MainWindow.xaml

@@ -7,7 +7,12 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
         xmlns:v="clr-namespace:ControlCatalog.Views"
-        x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}">
+        ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}"
+        ExtendClientAreaChromeHints="{Binding ChromeHints}"
+        ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
+        TransparencyLevelHint="{Binding TransparencyLevel}"        
+        x:Name="MainWindow"
+        x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
   <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="File">
@@ -56,20 +61,30 @@
     </NativeMenu>
   </NativeMenu.Menu>
 
- <Window.DataTemplates>
+  <Window.DataTemplates>
     <DataTemplate DataType="vm:NotificationViewModel">
       <v:CustomNotificationView />
     </DataTemplate>
   </Window.DataTemplates>
-  <DockPanel LastChildFill="True">
-    <Menu Name="MainMenu" DockPanel.Dock="Top">
-      <MenuItem Header="File">
-        <MenuItem Header="Exit" Command="{Binding ExitCommand}" />
-      </MenuItem>
-      <MenuItem Header="Help">
-        <MenuItem Header="About" Command="{Binding AboutCommand}" />
-      </MenuItem>
-    </Menu>
-    <local:MainView />
-  </DockPanel>
+  <Panel>
+    <Panel Margin="{Binding #MainWindow.OffScreenMargin}">
+      <DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}">
+        <Menu Name="MainMenu" DockPanel.Dock="Top">
+          <MenuItem Header="File">
+            <MenuItem Header="Exit" Command="{Binding ExitCommand}" />
+          </MenuItem>
+          <MenuItem Header="Help">
+            <MenuItem Header="About" Command="{Binding AboutCommand}" />
+          </MenuItem>
+        </Menu>
+        <local:MainView />
+      </DockPanel>
+    </Panel>
+    <Border IsVisible="{Binding ExtendClientAreaEnabled}" BorderThickness="1 1 1 0" CornerRadius="4 4 0 0" BorderBrush="#55000000" Height="22" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="250 8 0 0">
+      <Border.Background>
+        <SolidColorBrush Color="White" Opacity="0.7" />
+      </Border.Background>
+      <TextBlock Margin="5 5 5 0" Text="Content In Title Bar" />
+    </Border>
+  </Panel>
 </Window>

+ 139 - 0
samples/ControlCatalog/Pages/AcrylicPage.xaml

@@ -0,0 +1,139 @@
+<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"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.AcrylicPage">
+  <Border Padding="20" HorizontalAlignment="Center">
+    <StackPanel Spacing="20">
+
+      <ExperimentalAcrylicBorder Width="660" CornerRadius="5">
+        <ExperimentalAcrylicBorder.Material>
+          <ExperimentalAcrylicMaterial
+            TintColor="White"
+            BackgroundSource="Digger" />
+        </ExperimentalAcrylicBorder.Material>
+        <StackPanel Spacing="5" Margin="40 10">
+          <StackPanel Orientation="Horizontal">
+            <TextBlock Text="TintOpacity" Foreground="Black" />
+            <Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" Width="400" />
+            <TextBlock Text="{Binding #TintOpacitySlider.Value}" Foreground="Black" />
+          </StackPanel>
+          <StackPanel Orientation="Horizontal">
+            <TextBlock Text="MaterialOpacity" Foreground="Black" />
+            <Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" Width="400" />
+            <TextBlock Text="{Binding #MaterialOpacitySlider.Value}" Foreground="Black" />
+          </StackPanel>
+        </StackPanel>
+      </ExperimentalAcrylicBorder>
+
+
+      <StackPanel Orientation="Horizontal" Spacing="20">
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#FF0000"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#00FF00"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#000000"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+      </StackPanel>
+
+
+      <StackPanel Orientation="Horizontal" Spacing="20">
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#0000FF"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#FFFF00"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#000000"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+      </StackPanel>
+
+      <StackPanel Orientation="Horizontal" Spacing="20">
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="White"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#3c3c3c"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="White"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+      </StackPanel>
+
+      <ExperimentalAcrylicBorder Height="200" Width="660" CornerRadius="5">
+        <ExperimentalAcrylicBorder.Material>
+          <ExperimentalAcrylicMaterial
+            TintColor="Red"
+            TintOpacity="{Binding #TintOpacitySlider.Value}"
+            MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+            BackgroundSource="Digger" />
+        </ExperimentalAcrylicBorder.Material>
+      </ExperimentalAcrylicBorder>
+    </StackPanel>
+  </Border>
+</UserControl>

+ 19 - 0
samples/ControlCatalog/Pages/AcrylicPage.xaml.cs

@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class AcrylicPage : UserControl
+    {
+        public AcrylicPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 6 - 6
samples/ControlCatalog/Pages/BorderPage.xaml

@@ -9,27 +9,27 @@
                 Margin="0,16,0,0"
                 HorizontalAlignment="Center"
                 Spacing="16">
-      <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16">
+      <Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16">
         <TextBlock>Border</TextBlock>
       </Border>
-      <Border Background="{DynamicResource ThemeAccentBrush2}" 
-              BorderBrush="{DynamicResource ThemeAccentBrush}" 
+      <Border Background="{DynamicResource SystemAccentColorDark1}" 
+              BorderBrush="{DynamicResource SystemAccentColor}" 
               BorderThickness="4" 
               Padding="16">
         <TextBlock>Border and Background</TextBlock>
       </Border>
-      <Border BorderBrush="{DynamicResource ThemeAccentBrush}"
+      <Border BorderBrush="{DynamicResource SystemAccentColor}"
               BorderThickness="4" 
               CornerRadius="8"
               Padding="16">
         <TextBlock>Rounded Corners</TextBlock>
       </Border>
-      <Border Background="{DynamicResource ThemeAccentBrush2}"
+      <Border Background="{DynamicResource SystemAccentColor}"
               CornerRadius="8"
               Padding="16">
         <TextBlock>Rounded Corners</TextBlock>
       </Border>
-      <Border BorderBrush="{DynamicResource ThemeAccentBrush2}" Width="100" Height="100"
+      <Border Width="100" Height="100"
               BorderThickness="0"
               Background="White"
               CornerRadius="100" ClipToBounds="True">

+ 7 - 7
samples/ControlCatalog/Pages/ButtonPage.xaml

@@ -9,10 +9,10 @@
                 Margin="0,16,0,0"
                 HorizontalAlignment="Center"
                 Spacing="16">
-      <StackPanel Orientation="Vertical" Spacing="8" Width="150">
-        <Button>Button</Button>
+      <StackPanel Orientation="Vertical" Spacing="8" Width="200">
+        <Button>Standard XAML Button</Button>
         <Button Foreground="White">Foreground</Button>
-        <Button Background="{DynamicResource ThemeAccentBrush}">Background</Button>
+        <Button Background="{DynamicResource SystemAccentColor}">Background</Button>
         <Button IsEnabled="False">Disabled</Button>
         <Button Content="Re-themed">
           <Button.Styles>
@@ -32,10 +32,10 @@
 
       <StackPanel Orientation="Vertical" Spacing="8" Width="150">
         <Button BorderThickness="0">No Border</Button>
-        <Button BorderBrush="{DynamicResource ThemeAccentBrush}">Border Color</Button>
-        <Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4">Thick Border</Button>
-        <Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4" IsEnabled="False">Disabled</Button>
-        <Button BorderBrush="{DynamicResource ThemeAccentBrush}" KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
+        <Button BorderBrush="{DynamicResource SystemAccentColor}">Border Color</Button>
+        <Button BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="4">Thick Border</Button>
+        <Button BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="4" IsEnabled="False">Disabled</Button>
+        <Button BorderBrush="{DynamicResource SystemAccentColor}" KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
       </StackPanel>
     </StackPanel>    
   </StackPanel>

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

@@ -16,7 +16,7 @@
       <ComboBox SelectedIndex="0">
         <ComboBoxItem>
           <Panel>
-            <Rectangle Fill="{DynamicResource ThemeAccentBrush}"/>
+            <Rectangle Fill="{DynamicResource SystemAccentColor}"/>
             <TextBlock Margin="8">Control Items</TextBlock>
           </Panel>
         </ComboBoxItem>

+ 2 - 2
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@@ -9,7 +9,7 @@
               Margin="0,16,0,0"
               HorizontalAlignment="Center"
               Spacing="16">
-            <Border Background="{DynamicResource ThemeAccentBrush}"
+            <Border Background="{DynamicResource SystemAccentColor}"
                     Margin="16"
                     Padding="48,48,48,48">
                 <Border.ContextMenu>
@@ -34,7 +34,7 @@
                 </Border.ContextMenu>
                 <TextBlock Text="Defined in XAML"/>
             </Border>
-            <Border Background="{DynamicResource ThemeAccentBrush}"
+            <Border Background="{DynamicResource SystemAccentColor}"
                     Margin="16"
                     Padding="48,48,48,48">
                 <Border.ContextMenu>

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

@@ -10,15 +10,15 @@
                 HorizontalAlignment="Center"
                 Spacing="16">
             <StackPanel>
-                <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeText">
+                <Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16" Name="DragMeText">
                   <TextBlock Name="DragStateText">Drag Me</TextBlock>
                 </Border>
-                <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeCustom">
+                <Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16" Name="DragMeCustom">
                   <TextBlock Name="DragStateCustom">Drag Me (custom)</TextBlock>
                 </Border>
             </StackPanel>
 
-            <Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16" 
+            <Border Background="{DynamicResource SystemAccentColorDark1}" Padding="16" 
                     DragDrop.AllowDrop="True">
                 <TextBlock Name="DropState">Drop some text or files here</TextBlock>
             </Border>

+ 26 - 10
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@@ -1,6 +1,30 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.Pages.ItemsRepeaterPage">
+  <UserControl.Resources>
+    <RecyclePool x:Key="RecyclePool" />
+    <DataTemplate x:Key="odd">
+      <TextBlock Background="Yellow"
+                 Foreground="Black"
+                 Height="{Binding Height}"
+                 Text="{Binding Text}"/>
+    </DataTemplate>
+    <DataTemplate x:Key="even">
+      <TextBlock Background="Wheat"
+                 Foreground="Black"
+                 Height="{Binding Height}"
+                 Text="{Binding Text}"/>
+    </DataTemplate>
+    <RecyclingElementFactory x:Key="elementFactory"
+                             RecyclePool="{StaticResource RecyclePool}"
+                             SelectTemplateKey="OnSelectTemplateKey">
+      <RecyclingElementFactory.Templates>
+        <StaticResource x:Key="odd" ResourceKey="odd" />
+        <StaticResource x:Key="even" ResourceKey="even" />
+      </RecyclingElementFactory.Templates>
+    </RecyclingElementFactory>
+  </UserControl.Resources>
+  
   <DockPanel>
     <StackPanel DockPanel.Dock="Top" Spacing="4" Margin="0 0 0 16">
       <TextBlock Classes="h1">ItemsRepeater</TextBlock>
@@ -23,16 +47,8 @@
       <ScrollViewer Name="scroller"
                     HorizontalScrollBarVisibility="Auto"
                     VerticalScrollBarVisibility="Auto">
-        <ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}">
-          <ItemsRepeater.ItemTemplate>
-            <DataTemplate>
-              <TextBlock Focusable="True"
-                         Background="{Binding Background}"
-                         Height="{Binding Height}"
-                         Text="{Binding Text}"/>
-            </DataTemplate>
-          </ItemsRepeater.ItemTemplate>
-        </ItemsRepeater>
+        <ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}"
+                       ItemTemplate="{StaticResource elementFactory}"/>
       </ScrollViewer>
     </Border>
   </DockPanel>

+ 26 - 0
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -38,6 +38,12 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
+        public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e)
+        {
+            var item = (ItemsRepeaterPageViewModel.Item)e.DataContext;
+            e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
+        }
+
         private void LayoutChanged(object sender, SelectionChangedEventArgs e)
         {
             if (_repeater == null)
@@ -79,6 +85,26 @@ namespace ControlCatalog.Pages
                         MinItemHeight = 200,
                     };
                     break;
+                case 4:
+                    _scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
+                    _scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
+                    _repeater.Layout = new WrapLayout
+                    {
+                        Orientation = Orientation.Vertical,
+                        HorizontalSpacing = 20,
+                        VerticalSpacing = 20
+                    };
+                    break;
+                case 5:
+                    _scroller.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
+                    _scroller.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
+                    _repeater.Layout = new WrapLayout
+                    {
+                        Orientation = Orientation.Horizontal,
+                        HorizontalSpacing = 20,
+                        VerticalSpacing = 20
+                    };
+                    break;
             }
         }
 

+ 4 - 4
samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml

@@ -11,10 +11,10 @@
           RowDefinitions="24,Auto,24"
           HorizontalAlignment="Center"
           VerticalAlignment="Center">
-      <Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="0"/>
-      <Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="0" Grid.Row="1"/>
-      <Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="2" Grid.Row="1"/>
-      <Border Background="{DynamicResource ThemeAccentBrush}" Grid.Column="1" Grid.Row="2"/>
+      <Border Background="{DynamicResource SystemAccentColor}" Grid.Column="1" Grid.Row="0"/>
+      <Border Background="{DynamicResource SystemAccentColor}" Grid.Column="0" Grid.Row="1"/>
+      <Border Background="{DynamicResource SystemAccentColor}" Grid.Column="2" Grid.Row="1"/>
+      <Border Background="{DynamicResource SystemAccentColor}" Grid.Column="1" Grid.Row="2"/>
 
       <LayoutTransformControl Name="layoutTransform" Grid.Column="1" Grid.Row="1">
         <LayoutTransformControl.LayoutTransform>

+ 7 - 1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@@ -10,7 +10,13 @@
               HorizontalAlignment="Center"
               Spacing="16">
       <StackPanel Orientation="Vertical" Spacing="8">
-        <ListBox Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoScrollToSelectedItem="True"  SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
+        <ListBox Items="{Binding Items}"
+                 SelectedItem="{Binding SelectedItem}"
+                 SelectedItems="{Binding SelectedItems}"
+                 AutoScrollToSelectedItem="True"
+                 SelectionMode="{Binding SelectionMode}"
+                 Width="250"
+                 Height="350"/>
 
         <Button Command="{Binding AddItemCommand}">Add</Button>
 

+ 60 - 0
samples/ControlCatalog/Pages/RelativePanelPage.axaml

@@ -0,0 +1,60 @@
+<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"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.RelativePanelPage">
+    <RelativePanel Width="620" Height="700" Margin="32">
+        <Border Name="Rect1" Background="Red" Height="50" Width="50">
+            <TextBlock Text="Rect1" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect2" Background="Blue" Height="50" Width="50" RelativePanel.AlignHorizontalCenterWithPanel="True">
+            <TextBlock Text="Rect2" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect3" Background="Green" Height="50" Width="50" RelativePanel.AlignRightWithPanel="True">
+            <TextBlock Text="Rect3" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect4" Background="Red" Height="50" Width="50" RelativePanel.AlignBottomWithPanel="True">
+            <TextBlock Text="Rect4" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect5" Background="Blue" Height="50" Width="50" RelativePanel.AlignBottomWithPanel="True" RelativePanel.AlignHorizontalCenterWithPanel="True">
+            <TextBlock Text="Rect5" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect6" Background="Green" Height="50" Width="50" RelativePanel.AlignBottomWithPanel="True" RelativePanel.AlignRightWithPanel="True">
+            <TextBlock Text="Rect6" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect7" Background="Blue" Height="50" RelativePanel.RightOf="{Binding ElementName=Rect1}">
+            <TextBlock Text="Rect7 (RightOf Rect1)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect8" Background="Green" Height="50" RelativePanel.Below="{Binding ElementName=Rect7}">
+            <TextBlock Text="Rect8 (Below Rect7)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect9" Background="Blue" Height="140" Width="460" RelativePanel.AlignHorizontalCenterWithPanel="True" RelativePanel.AlignVerticalCenterWithPanel="True">
+            <TextBlock Text="Rect9" Padding="10" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Top"/>
+        </Border>
+        <Border Name="Rect10" Background="Red" Width="50" RelativePanel.RightOf="{Binding ElementName=Rect9}" RelativePanel.AlignVerticalCenterWith="{Binding ElementName=Rect9}">
+            <TextBlock Text="Rect14 (RightOf Rect9, AlignVerticalCenterWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center">
+                <TextBlock.RenderTransform>
+                    <TransformGroup>
+                        <RotateTransform Angle="-90"/>
+                    </TransformGroup>
+                </TextBlock.RenderTransform>
+            </TextBlock>
+        </Border>
+        <Border Name="Rect11" Background="Red" Height="50" RelativePanel.AlignBottomWith="{Binding ElementName=Rect9}" RelativePanel.AlignHorizontalCenterWith="{Binding ElementName=Rect9}">
+            <TextBlock Text="Rect11 (AlignBottomWith Rect9, AlignHorizontalCenterWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect12" Background="Red" Height="50" RelativePanel.Below="{Binding ElementName=Rect8}" RelativePanel.AlignLeftWith="{Binding ElementName=Rect7}">
+            <TextBlock Text="Rect12 (Below Rect8, AlignLeftWith Rect7)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect13" Background="Blue" Height="50" RelativePanel.Below="{Binding ElementName=Rect12}" RelativePanel.AlignRightWith="{Binding ElementName=Rect12}">
+            <TextBlock Text="Rect13 (Below Rect12, AlignRightWith Rect12)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect14" Background="Green" Height="50" RelativePanel.Above="{Binding ElementName=Rect9}" RelativePanel.AlignRightWith="{Binding ElementName=Rect9}">
+            <TextBlock Text="Rect14 (Above Rect9, AlignRightWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+        <Border Name="Rect15" Background="Red" Height="50" RelativePanel.LeftOf="{Binding ElementName=Rect2}" RelativePanel.AlignTopWith="{Binding ElementName=Rect9}">
+            <TextBlock Text="Rect15 (LeftOf Rect2, AlignTopWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+        </Border>
+    </RelativePanel>
+</UserControl>

+ 19 - 0
samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs

@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class RelativePanelPage : UserControl
+    {
+        public RelativePanelPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 30 - 39
samples/ControlCatalog/Pages/TextBlockPage.xaml

@@ -64,51 +64,42 @@
               <TextDecorationCollection>
                 <TextDecoration
                   Location="Overline"
-                  PenThicknessUnit="Pixel">
-                  <TextDecoration.Pen>
-                    <Pen Thickness="2">
-                      <Pen.Brush>
-                        <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
-                          <LinearGradientBrush.GradientStops>
-                            <GradientStop Offset="0" Color="Red"/>
-                            <GradientStop Offset="1" Color="Green"/>
-                          </LinearGradientBrush.GradientStops>
-                        </LinearGradientBrush>
-                      </Pen.Brush>
-                    </Pen>
-                  </TextDecoration.Pen>
+                  StrokeThicknessUnit="Pixel"
+                  StrokeThickness="2">
+                  <TextDecoration.Stroke>
+                    <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
+                      <LinearGradientBrush.GradientStops>
+                        <GradientStop Offset="0" Color="Red"/>
+                        <GradientStop Offset="1" Color="Green"/>
+                      </LinearGradientBrush.GradientStops>
+                    </LinearGradientBrush>
+                  </TextDecoration.Stroke>
                 </TextDecoration>
                 <TextDecoration
                   Location="Strikethrough"
-                  PenThicknessUnit="Pixel">
-                  <TextDecoration.Pen>
-                    <Pen Thickness="1">
-                      <Pen.Brush>
-                        <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
-                          <LinearGradientBrush.GradientStops>
-                            <GradientStop Offset="0" Color="Green"/>
-                            <GradientStop Offset="1" Color="Blue"/>
-                          </LinearGradientBrush.GradientStops>
-                        </LinearGradientBrush>
-                      </Pen.Brush>
-                    </Pen>
-                  </TextDecoration.Pen>
+                  StrokeThicknessUnit="Pixel"
+                  StrokeThickness="1">
+                  <TextDecoration.Stroke>
+                    <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
+                      <LinearGradientBrush.GradientStops>
+                        <GradientStop Offset="0" Color="Green"/>
+                        <GradientStop Offset="1" Color="Blue"/>
+                      </LinearGradientBrush.GradientStops>
+                    </LinearGradientBrush>
+                  </TextDecoration.Stroke>
                 </TextDecoration>
                 <TextDecoration
                   Location="Underline"
-                  PenThicknessUnit="Pixel">
-                  <TextDecoration.Pen>
-                    <Pen Thickness="2">
-                      <Pen.Brush>
-                        <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
-                          <LinearGradientBrush.GradientStops>
-                            <GradientStop Offset="0" Color="Blue"/>
-                            <GradientStop Offset="1" Color="Red"/>
-                          </LinearGradientBrush.GradientStops>
-                        </LinearGradientBrush>
-                      </Pen.Brush>
-                    </Pen>
-                  </TextDecoration.Pen>
+                  StrokeThicknessUnit="Pixel"
+                  StrokeThickness="2">
+                  <TextDecoration.Stroke>
+                    <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
+                      <LinearGradientBrush.GradientStops>
+                        <GradientStop Offset="0" Color="Blue"/>
+                        <GradientStop Offset="1" Color="Red"/>
+                      </LinearGradientBrush.GradientStops>
+                    </LinearGradientBrush>
+                  </TextDecoration.Stroke>
                 </TextDecoration>
               </TextDecorationCollection>
             </TextBlock.TextDecorations>

+ 2 - 2
samples/ControlCatalog/Pages/ToolTipPage.xaml

@@ -12,7 +12,7 @@
               HorizontalAlignment="Center">
             <Border Grid.Column="0"
                     Grid.Row="1"
-                    Background="{DynamicResource ThemeAccentBrush}"
+                    Background="{DynamicResource SystemAccentColor}"
                     Margin="5"
                     Padding="50"
                     ToolTip.Tip="This is a ToolTip">
@@ -26,7 +26,7 @@
             <Border Name="Border"
                     Grid.Column="1"
                     Grid.Row="1"
-                    Background="{DynamicResource ThemeAccentBrush}"
+                    Background="{DynamicResource SystemAccentColor}"
                     Margin="5"
                     Padding="50"
                     ToolTip.Placement="Bottom">

+ 19 - 0
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@@ -0,0 +1,19 @@
+<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"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.WindowCustomizationsPage">
+  <StackPanel Spacing="10"  Margin="25">
+    <CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" />
+    <CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" />    
+    <CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" />
+    <Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" />
+    <ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
+      <ComboBoxItem>None</ComboBoxItem>
+      <ComboBoxItem>Transparent</ComboBoxItem>
+      <ComboBoxItem>Blur</ComboBoxItem>
+      <ComboBoxItem>AcrylicBlur</ComboBoxItem>
+    </ComboBox>
+  </StackPanel>
+</UserControl>

+ 19 - 0
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs

@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class WindowCustomizationsPage : UserControl
+    {
+        public WindowCustomizationsPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 3 - 3
samples/ControlCatalog/SideBar.xaml

@@ -12,7 +12,7 @@
     <Style Selector="TabControl.sidebar">
         <Setter Property="TabStripPlacement" Value="Left"/>
         <Setter Property="Padding" Value="8 0 0 0"/>
-        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
+        <Setter Property="Background" Value="{DynamicResource SystemAccentColor}"/>
         <Setter Property="Template">
             <ControlTemplate>
                 <Border 
@@ -74,12 +74,12 @@
         <Setter Property="Opacity" Value="1"/>
     </Style>
     <Style Selector="TabControl.sidebar > TabItem:pointerover">
-        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Background" Value="{DynamicResource SystemAccentColorLight2}"/>
     </Style>
     <Style Selector="TabControl.sidebar > TabItem:selected">
         <Setter Property="Opacity" Value="1"/>
     </Style>
     <Style Selector="TabControl.sidebar > TabItem:selected">
-        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
+        <Setter Property="Background" Value="{DynamicResource SystemAccentColorLight1}"/>
     </Style>
 </Styles>

+ 2 - 8
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@@ -62,13 +62,9 @@ namespace ControlCatalog.ViewModels
         public class Item : ReactiveObject
         {
             private double _height = double.NaN;
-            private int _index;
-
-            public Item(int index)
-            {
-                _index = index;
-            }
 
+            public Item(int index) => Index = index;
+            public int Index { get; }
             public string Text { get; set; }
             
             public double Height 
@@ -76,8 +72,6 @@ namespace ControlCatalog.ViewModels
                 get => _height;
                 set => this.RaiseAndSetIfChanged(ref _height, value);
             }
-
-            public IBrush Background => ((_index % 2) == 0) ? Brushes.Yellow : Brushes.Wheat;
         }
     }
 }

+ 65 - 0
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@@ -3,6 +3,8 @@ using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Notifications;
 using Avalonia.Dialogs;
+using Avalonia.Platform;
+using System;
 using ReactiveUI;
 
 namespace ControlCatalog.ViewModels
@@ -14,6 +16,12 @@ namespace ControlCatalog.ViewModels
         private bool _isMenuItemChecked = true;
         private WindowState _windowState;
         private WindowState[] _windowStates;
+        private int _transparencyLevel;
+        private ExtendClientAreaChromeHints _chromeHints;
+        private bool _extendClientAreaEnabled;
+        private bool _systemTitleBarEnabled;        
+        private bool _preferSystemChromeEnabled;
+        private double _titleBarHeight;
 
         public MainWindowViewModel(IManagedNotificationManager notificationManager)
         {
@@ -62,6 +70,63 @@ namespace ControlCatalog.ViewModels
                 WindowState.Maximized,
                 WindowState.FullScreen,
             };
+
+            this.WhenAnyValue(x => x.SystemTitleBarEnabled, x=>x.PreferSystemChromeEnabled)
+                .Subscribe(x =>
+                {
+                    var hints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
+
+                    if(x.Item1)
+                    {
+                        hints |= ExtendClientAreaChromeHints.SystemChrome;
+                    }
+
+                    if(x.Item2)
+                    {
+                        hints |= ExtendClientAreaChromeHints.PreferSystemChrome;
+                    }
+
+                    ChromeHints = hints;
+                });
+
+            SystemTitleBarEnabled = true;            
+            TitleBarHeight = -1;
+        }        
+
+        public int TransparencyLevel
+        {
+            get { return _transparencyLevel; }
+            set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); }
+        }        
+
+        public ExtendClientAreaChromeHints ChromeHints
+        {
+            get { return _chromeHints; }
+            set { this.RaiseAndSetIfChanged(ref _chromeHints, value); }
+        }        
+
+        public bool ExtendClientAreaEnabled
+        {
+            get { return _extendClientAreaEnabled; }
+            set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); }
+        }        
+
+        public bool SystemTitleBarEnabled
+        {
+            get { return _systemTitleBarEnabled; }
+            set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); }
+        }        
+
+        public bool PreferSystemChromeEnabled
+        {
+            get { return _preferSystemChromeEnabled; }
+            set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); }
+        }        
+
+        public double TitleBarHeight
+        {
+            get { return _titleBarHeight; }
+            set { this.RaiseAndSetIfChanged(ref _titleBarHeight, value); }
         }
 
         public WindowState WindowState

+ 2 - 3
samples/RenderDemo/App.xaml

@@ -3,8 +3,7 @@
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     x:Class="RenderDemo.App">
     <Application.Styles>
-        <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
-        <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
+        <StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentLight.xaml"/>
         <StyleInclude Source="avares://RenderDemo/SideBar.xaml"/>
     </Application.Styles>
-</Application>
+</Application>

+ 2 - 2
samples/RenderDemo/Pages/ClippingPage.xaml

@@ -43,7 +43,7 @@
                   C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344 
                   C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812
                   C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z ">
-      <Border Name="clipChild" Background="{DynamicResource ThemeAccentBrush}" Margin="4">
+      <Border Name="clipChild" Background="Red" Margin="4">
         <!-- Setting opacity puts the TextBox on a new layer -->
         <TextBox Text="Avalonia" Opacity="0.9" VerticalAlignment="Center"/>
         <Border.RenderTransform>
@@ -53,4 +53,4 @@
     </Border>
     <CheckBox Name="useMask" IsChecked="True" Grid.Row="1">Apply Geometry Clip</CheckBox>
   </Grid>
-</UserControl>
+</UserControl>

+ 64 - 61
samples/RenderDemo/SideBar.xaml

@@ -1,65 +1,68 @@
 <Styles xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-    <Style Selector="TabControl.sidebar">
-        <Setter Property="TabStripPlacement" Value="Left"/>
-        <Setter Property="Padding" Value="8 0 0 0"/>
-        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
-        <Setter Property="Template">
-            <ControlTemplate>
-                <Border 
-                    Margin="{TemplateBinding Margin}"
-                    BorderBrush="{TemplateBinding BorderBrush}"
-                    BorderThickness="{TemplateBinding BorderThickness}">
-                    <DockPanel>
-                        <ScrollViewer
-                            Name="PART_ScrollViewer"
-                            HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
-                            VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
-                            Background="{TemplateBinding Background}">
-                            <ItemsPresenter
-                                Name="PART_ItemsPresenter"                          
-                                Items="{TemplateBinding Items}"
-                                ItemsPanel="{TemplateBinding ItemsPanel}"
-                                ItemTemplate="{TemplateBinding ItemTemplate}">
-                            </ItemsPresenter>
-                        </ScrollViewer>
-                        <ContentPresenter
-                            Name="PART_SelectedContentHost"
-                            Margin="{TemplateBinding Padding}"                           
-                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
-                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
-                            Content="{TemplateBinding SelectedContent}"
-                            ContentTemplate="{TemplateBinding SelectedContentTemplate}">
-                        </ContentPresenter>
-                    </DockPanel>
-                </Border>
-            </ControlTemplate>
-        </Setter>
-    </Style>
+  <Style Selector="TabControl.sidebar">
+    <Setter Property="TabStripPlacement" Value="Left"/>
+    <Setter Property="Padding" Value="8 0 0 0"/>
+    <Setter Property="Background" Value="{DynamicResource SystemAccentColor}"/>
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Border
+            Margin="{TemplateBinding Margin}"
+            BorderBrush="{TemplateBinding BorderBrush}"
+            BorderThickness="{TemplateBinding BorderThickness}">
+          <DockPanel>
+            <ScrollViewer
+                Name="PART_ScrollViewer"
+                HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
+                VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+                Background="{TemplateBinding Background}">
+              <ItemsPresenter
+                  Name="PART_ItemsPresenter"
+                  Items="{TemplateBinding Items}"
+                  ItemsPanel="{TemplateBinding ItemsPanel}"
+                  ItemTemplate="{TemplateBinding ItemTemplate}">
+              </ItemsPresenter>
+            </ScrollViewer>
+            <ContentPresenter
+                Name="PART_SelectedContentHost"
+                Margin="{TemplateBinding Padding}"
+                HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                Content="{TemplateBinding SelectedContent}"
+                ContentTemplate="{TemplateBinding SelectedContentTemplate}">
+            </ContentPresenter>
+          </DockPanel>
+        </Border>
+      </ControlTemplate>
+    </Setter>
+  </Style>
 
-    <Style Selector="TabControl.sidebar > TabItem">       
-        <Setter Property="BorderThickness" Value="0"/>
-        <Setter Property="Foreground" Value="White"/>
-        <Setter Property="FontSize" Value="14"/>
-        <Setter Property="Margin" Value="0"/>
-        <Setter Property="Padding" Value="16"/>
-        <Setter Property="Opacity" Value="0.5"/>
-        <Setter Property="Transitions">
-            <Transitions>
-                <DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
-            </Transitions>
-        </Setter>
-    </Style>
-    <Style Selector="TabControl.sidebar > TabItem:pointerover">
-        <Setter Property="Opacity" Value="1"/>
-    </Style>
-    <Style Selector="TabControl.sidebar > TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
-        <Setter Property="Background" Value="Transparent"/>
-    </Style>
-    <Style Selector="TabControl.sidebar > TabItem:selected">
-        <Setter Property="Opacity" Value="1"/>
-    </Style>
-    <Style Selector="TabControl.sidebar > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
-        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
-    </Style>
+  <Style Selector="TabControl.sidebar > TabItem">
+    <Setter Property="BorderThickness" Value="0"/>
+    <Setter Property="Foreground" Value="White"/>
+    <Setter Property="FontSize" Value="14"/>
+    <Setter Property="Margin" Value="0"/>
+    <Setter Property="Padding" Value="16"/>
+    <Setter Property="Opacity" Value="0.5"/>
+    <Setter Property="Transitions">
+      <Transitions>
+        <DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
+      </Transitions>
+    </Setter>
+  </Style>
+  <Style Selector="TabControl.sidebar > TabItem:selected /template/ Border#PART_SelectedPipe">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+  <Style Selector="TabControl.sidebar > TabItem:pointerover">
+    <Setter Property="Opacity" Value="1"/>
+  </Style>
+  <Style Selector="TabControl.sidebar > TabItem:pointerover /template/ Border#PART_LayoutRoot">
+    <Setter Property="Background" Value="{DynamicResource SystemAccentColorLight2}"/>
+  </Style>
+  <Style Selector="TabControl.sidebar > TabItem:selected">
+    <Setter Property="Opacity" Value="1"/>
+  </Style>
+  <Style Selector="TabControl.sidebar > TabItem:selected /template/ Border#PART_LayoutRoot">
+    <Setter Property="Background" Value="{DynamicResource SystemAccentColorLight1}"/>
+  </Style>
 </Styles>

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

@@ -10,6 +10,7 @@
   <ItemGroup>
     <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
     <ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.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.2019013001" />

+ 1 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _view.Visibility = ViewStates.Visible;
         }
 
-        public double Scaling => 1;
+        public double RenderScaling => 1;
 
         void Draw()
         {

+ 7 - 1
src/Avalonia.Base/AvaloniaProperty.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Reactive.Subjects;
 using Avalonia.Data;
+using Avalonia.Data.Core;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -9,7 +10,7 @@ namespace Avalonia
     /// <summary>
     /// Base class for avalonia properties.
     /// </summary>
-    public abstract class AvaloniaProperty : IEquatable<AvaloniaProperty>
+    public abstract class AvaloniaProperty : IEquatable<AvaloniaProperty>, IPropertyInfo
     {
         /// <summary>
         /// Represents an unset property value.
@@ -582,6 +583,11 @@ namespace Avalonia
 
             return _defaultMetadata;
         }
+
+        bool IPropertyInfo.CanGet => true;
+        bool IPropertyInfo.CanSet => true;
+        object IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
+        void IPropertyInfo.Set(object target, object value) => ((AvaloniaObject)target).SetValue(this, value);
     }
 
     /// <summary>

+ 73 - 0
src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Avalonia.Data.Core
+{
+    public class ClrPropertyInfo : IPropertyInfo
+    {
+        private readonly Func<object, object> _getter;
+        private readonly Action<object, object> _setter;
+
+        public ClrPropertyInfo(string name, Func<object, object> getter, Action<object, object> setter, Type propertyType)
+        {
+            _getter = getter;
+            _setter = setter;
+            PropertyType = propertyType;
+            Name = name;
+        }
+
+        public string Name { get; }
+        public Type PropertyType { get; }
+
+        public object Get(object target)
+        {
+            if (_getter == null)
+                throw new NotSupportedException("Property " + Name + " doesn't have a getter");
+            return _getter(target);
+        }
+
+        public void Set(object target, object value)
+        {
+            if (_setter == null)
+                throw new NotSupportedException("Property " + Name + " doesn't have a setter");
+            _setter(target, value);
+        }
+
+        public bool CanSet => _setter != null;
+        public bool CanGet => _getter != null;
+    }
+
+    public class ReflectionClrPropertyInfo : ClrPropertyInfo
+    {
+        static Action<object, object> CreateSetter(PropertyInfo info)
+        {
+            if (info.SetMethod == null)
+                return null;
+            var target = Expression.Parameter(typeof(object), "target");
+            var value = Expression.Parameter(typeof(object), "value");
+            return Expression.Lambda<Action<object, object>>(
+                    Expression.Call(Expression.Convert(target, info.DeclaringType), info.SetMethod,
+                        Expression.Convert(value, info.SetMethod.GetParameters()[0].ParameterType)),
+                    target, value)
+                .Compile();
+        }
+        
+        static Func<object, object> CreateGetter(PropertyInfo info)
+        {
+            if (info.GetMethod == null)
+                return null;
+            var target = Expression.Parameter(typeof(object), "target");
+            return Expression.Lambda<Func<object, object>>(
+                    Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType), info.GetMethod),
+                        typeof(object)))
+                .Compile();
+        }
+
+        public ReflectionClrPropertyInfo(PropertyInfo info) : base(info.Name,
+            CreateGetter(info), CreateSetter(info), info.PropertyType)
+        {
+            
+        }
+    }
+}

+ 23 - 9
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@@ -54,6 +54,7 @@ namespace Avalonia.Data.Core
         private object _root;
         private IDisposable _rootSubscription;
         private WeakReference<object> _value;
+        private IReadOnlyList<ITransformNode> _transformNodes;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@@ -188,6 +189,24 @@ namespace Avalonia.Data.Core
                 description ?? expression.ToString());
         }
 
+        private IReadOnlyList<ITransformNode> GetTransformNodesFromChain()
+        {
+            LinkedList<ITransformNode> transforms = new LinkedList<ITransformNode>();
+            var node = _node;
+            while (node != null)
+            {
+                if (node is ITransformNode transform)
+                {
+                    transforms.AddFirst(transform);
+                }
+                node = node.Next;
+            }
+
+            return new List<ITransformNode>(transforms);
+        }
+
+        private IReadOnlyList<ITransformNode> TransformNodes => (_transformNodes ?? (_transformNodes = GetTransformNodesFromChain()));
+
         /// <summary>
         /// Attempts to set the value of a property expression.
         /// </summary>
@@ -203,18 +222,13 @@ namespace Avalonia.Data.Core
         {
             if (Leaf is SettableNode settable)
             {
-                var node = _node;
-                while (node != null)
+                foreach (var transform in TransformNodes)
                 {
-                    if (node is ITransformNode transform)
+                    value = transform.Transform(value);
+                    if (value is BindingNotification)
                     {
-                        value = transform.Transform(value);
-                        if (value is BindingNotification)
-                        {
-                            return false;
-                        }
+                        return false;
                     }
-                    node = node.Next;
                 }
                 return settable.SetTargetValue(value, priority);
             }

+ 14 - 0
src/Avalonia.Base/Data/Core/IPropertyInfo.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Avalonia.Data.Core
+{
+    public interface IPropertyInfo
+    {
+        string Name { get; }
+        object Get(object target);
+        void Set(object target, object value);
+        bool CanSet { get; }
+        bool CanGet { get; }
+        Type PropertyType { get; }
+    }
+}

+ 1 - 1
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@@ -59,7 +59,7 @@ namespace Avalonia.Data.Core.Plugins
             return Observable.Empty<object>();
         }
 
-        protected IObservable<object> HandleCompleted(Task task)
+        private IObservable<object> HandleCompleted(Task task)
         {
             var resultProperty = task.GetType().GetRuntimeProperty("Result");
             

+ 21 - 11
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@@ -7,6 +7,7 @@ namespace Avalonia.Data.Core
     public class PropertyAccessorNode : SettableNode
     {
         private readonly bool _enableValidation;
+        private IPropertyAccessorPlugin _customPlugin;
         private IPropertyAccessor _accessor;
 
         public PropertyAccessorNode(string propertyName, bool enableValidation)
@@ -15,6 +16,13 @@ namespace Avalonia.Data.Core
             _enableValidation = enableValidation;
         }
 
+        public PropertyAccessorNode(string propertyName, bool enableValidation, IPropertyAccessorPlugin customPlugin)
+        {
+            PropertyName = propertyName;
+            _enableValidation = enableValidation;
+            _customPlugin = customPlugin;
+        }
+
         public override string Description => PropertyName;
         public string PropertyName { get; }
         public override Type PropertyType => _accessor?.PropertyType;
@@ -37,17 +45,7 @@ namespace Avalonia.Data.Core
         {
             reference.TryGetTarget(out object target);
 
-            IPropertyAccessorPlugin plugin = null;
-
-            foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
-            {
-                if (x.Match(target, PropertyName))
-                {
-                    plugin = x;
-                    break;
-                }
-            }
-
+            var plugin = _customPlugin ?? GetPropertyAccessorPluginForObject(target);
             var accessor = plugin?.Start(reference, PropertyName);
 
             // We need to handle accessor fallback before handling validation. Validators do not support null accessors.
@@ -82,6 +80,18 @@ namespace Avalonia.Data.Core
             accessor.Subscribe(ValueChanged);
         }
 
+        private IPropertyAccessorPlugin GetPropertyAccessorPluginForObject(object target)
+        {
+            foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
+            {
+                if (x.Match(target, PropertyName))
+                {
+                    return x;
+                }
+            }
+            return null;
+        }
+
         protected override void StopListeningCore()
         {
             _accessor.Dispose();

+ 91 - 0
src/Avalonia.Base/Data/Core/PropertyPath.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.Data.Core
+{
+    public class PropertyPath
+    {
+        public IReadOnlyList<IPropertyPathElement> Elements { get; }
+
+        public PropertyPath(IEnumerable<IPropertyPathElement> elements)
+        {
+            Elements = elements.ToList();
+        }
+    }
+
+    public class PropertyPathBuilder
+    {
+        readonly List<IPropertyPathElement> _elements = new List<IPropertyPathElement>();
+        
+        public PropertyPathBuilder Property(IPropertyInfo property)
+        {
+            _elements.Add(new PropertyPropertyPathElement(property));
+            return this;
+        }
+        
+
+        public PropertyPathBuilder ChildTraversal()
+        {
+            _elements.Add(new ChildTraversalPropertyPathElement());
+            return this;
+        }
+
+        public PropertyPathBuilder EnsureType(Type type)
+        {
+            _elements.Add(new EnsureTypePropertyPathElement(type));
+            return this;
+        }
+
+        public PropertyPathBuilder Cast(Type type)
+        {
+            _elements.Add(new CastTypePropertyPathElement(type));
+            return this;
+        }
+
+        public PropertyPath Build()
+        {
+            return new PropertyPath(_elements);
+        }
+    }
+
+    public interface IPropertyPathElement
+    {
+        
+    }
+
+    public class PropertyPropertyPathElement : IPropertyPathElement
+    {
+        public IPropertyInfo Property { get; }
+
+        public PropertyPropertyPathElement(IPropertyInfo property)
+        {
+            Property = property;
+        }
+    }
+
+    public class ChildTraversalPropertyPathElement : IPropertyPathElement
+    {
+        
+    }
+
+    public class EnsureTypePropertyPathElement : IPropertyPathElement
+    {
+        public Type Type { get; }
+
+        public EnsureTypePropertyPathElement(Type type)
+        {
+            Type = type;
+        }
+    }
+
+    public class CastTypePropertyPathElement : IPropertyPathElement
+    {
+        public CastTypePropertyPathElement(Type type)
+        {
+            Type = type;
+        }
+
+        public Type Type { get; }
+    }
+}

+ 28 - 9
src/Avalonia.Base/Data/Core/StreamNode.cs

@@ -1,35 +1,54 @@
 using System;
 using System.Reactive.Linq;
+using Avalonia.Data.Core.Plugins;
 
 namespace Avalonia.Data.Core
 {
     public class StreamNode : ExpressionNode
     {
+        private IStreamPlugin _customPlugin = null;
         private IDisposable _subscription;
 
         public override string Description => "^";
 
+        public StreamNode() { }
+
+        public StreamNode(IStreamPlugin customPlugin)
+        {
+            _customPlugin = customPlugin;
+        }
+
         protected override void StartListeningCore(WeakReference<object> reference)
         {
+            GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged);
+        }
+
+        protected override void StopListeningCore()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+        }
+
+        private IStreamPlugin GetPlugin(WeakReference<object> reference)
+        {
+            if (_customPlugin != null)
+            {
+                return _customPlugin;
+            }
+
             foreach (var plugin in ExpressionObserver.StreamHandlers)
             {
                 if (plugin.Match(reference))
                 {
-                    _subscription = plugin.Start(reference).Subscribe(ValueChanged);
-                    return;
+                    return plugin;
                 }
             }
 
-            // TODO: Improve error.
+            // TODO: Improve error
             ValueChanged(new BindingNotification(
                 new MarkupBindingChainException("Stream operator applied to unsupported type", Description),
                 BindingErrorType.Error));
-        }
-
-        protected override void StopListeningCore()
-        {
-            _subscription?.Dispose();
-            _subscription = null;
+            return null;
         }
     }
 }

+ 26 - 1
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@@ -1,3 +1,5 @@
+using System;
+using System.Runtime.ConstrainedExecution;
 using System.Threading;
 
 namespace Avalonia.Threading
@@ -7,6 +9,20 @@ namespace Avalonia.Threading
     /// </summary>
     public class AvaloniaSynchronizationContext : SynchronizationContext
     {
+        public interface INonPumpingPlatformWaitProvider
+        {
+            int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
+        }
+
+        private readonly INonPumpingPlatformWaitProvider _waitProvider;
+
+        public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider)
+        {
+            _waitProvider = waitProvider;
+            if (_waitProvider != null)
+                SetWaitNotificationRequired();
+        }
+
         /// <summary>
         /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer.
         /// </summary>
@@ -22,7 +38,8 @@ namespace Avalonia.Threading
                 return;
             }
 
-            SetSynchronizationContext(new AvaloniaSynchronizationContext());
+            SetSynchronizationContext(new AvaloniaSynchronizationContext(AvaloniaLocator.Current
+                .GetService<INonPumpingPlatformWaitProvider>()));
         }
 
         /// <inheritdoc/>
@@ -39,5 +56,13 @@ namespace Avalonia.Threading
             else
                 Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
         }
+
+        [PrePrepareMethod]
+        public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
+        {
+            if (_waitProvider != null)
+                return _waitProvider.Wait(waitHandles, waitAll, millisecondsTimeout);
+            return base.Wait(waitHandles, waitAll, millisecondsTimeout);
+        }
     }
 }

+ 20 - 0
src/Avalonia.Base/Utilities/CharacterReader.cs

@@ -79,5 +79,25 @@ namespace Avalonia.Utilities
             Position += len;
             return span;
         }
+
+        public ReadOnlySpan<char> TryPeek(int count)
+        {
+            if (_s.Length < count)
+                return ReadOnlySpan<char>.Empty;
+            return _s.Slice(0, count);
+        }
+
+        public ReadOnlySpan<char> PeekWhitespace()
+        {
+            var trimmed = _s.TrimStart();
+            return _s.Slice(0, _s.Length - trimmed.Length);
+        }
+
+        public void Skip(int count)
+        {
+            if (_s.Length < count)
+                throw new IndexOutOfRangeException();
+            _s = _s.Slice(count);
+        }
     }
 }

+ 46 - 0
src/Avalonia.Base/Utilities/KeywordParser.cs

@@ -0,0 +1,46 @@
+using System;
+
+namespace Avalonia.Utilities
+{
+#if !BUILDTASK
+    public
+#endif
+    static class KeywordParser
+    {
+        public static bool CheckKeyword(this ref CharacterReader r, string keyword)
+        {
+            return (CheckKeywordInternal(ref r, keyword) >= 0);
+        }
+        
+        static int CheckKeywordInternal(this ref CharacterReader r, string keyword)
+        {
+            var ws = r.PeekWhitespace();
+
+            var chars = r.TryPeek(ws.Length + keyword.Length);
+            if (chars.IsEmpty)
+                return -1;
+            if (SpanEquals(chars.Slice(ws.Length), keyword.AsSpan()))
+                return chars.Length;
+            return -1;
+        }
+
+        static bool SpanEquals(ReadOnlySpan<char> left, ReadOnlySpan<char> right)
+        {
+            if (left.Length != right.Length)
+                return false;
+            for(var c=0; c<left.Length;c++)
+                if (left[c] != right[c])
+                    return false;
+            return true;
+        }
+
+        public static bool TakeIfKeyword(this ref CharacterReader r, string keyword)
+        {
+            var l = CheckKeywordInternal(ref r, keyword);
+            if (l < 0)
+                return false;
+            r.Skip(l);
+            return true;
+        }
+    }
+}

+ 19 - 27
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -5,7 +5,7 @@
         <OutputType>exe</OutputType>
         <GenerateDocumentationFile>false</GenerateDocumentationFile>
         <BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
-        <DefineConstants>$(DefineConstants);BUILDTASK;XAMLIL_CECIL_INTERNAL;XAMLIL_INTERNAL</DefineConstants>
+        <DefineConstants>$(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL</DefineConstants>
         <CopyLocalLockFileAssemblies Condition="$(TargetFramework) == 'netstandard2.0'">true</CopyLocalLockFileAssemblies>
         <NoWarn>NU1605</NoWarn>
     </PropertyGroup>
@@ -17,19 +17,22 @@
       <Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
         <Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
       </Compile>
-      <Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/**/*.cs">
+      <Compile Include="../Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/**/*.cs">
         <Link>XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
       <Compile Remove="external/cecil/**/*.*" />
-      <Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\**\*.cs">
+      <Compile Include="../Markup/Avalonia.Markup.Xaml.Loader/xamlil.github\src\XamlX\**\*.cs">
         <Link>XamlIl/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
-      <Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs">
+      <Compile Include="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX.IL.Cecil\**\*.cs">
         <Link>XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
       <Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
+      <Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\PropertyPathGrammar.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
       <Compile Include="../Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
@@ -38,36 +41,25 @@
       </Compile>
       <Compile Include="../Avalonia.Base/Utilities/CharacterReader.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
-      </Compile>      
+      </Compile>
       <Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
+      <Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
+      <Compile Include="../Avalonia.Base/Utilities/KeywordParser.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
+      <Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
       <Compile Include="../Avalonia.Base/Utilities/StyleClassParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
-
-      <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
-      <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\TypeSystem\SreTypeSystem.cs" />
+      <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\**\obj\**\*.cs" />
+      <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
       <PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />
-      <PackageReference Condition="$(TargetFramework) == 'netstandard2.0'" Include="ILRepack.MSBuild.Task" Version="2.0.13" PrivateAssets="All" />
       <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
     </ItemGroup>
-
-  <Target Name="ILRepack" AfterTargets="Build" Condition="$(TargetFramework) == 'netstandard2.0'">
-
-    <PropertyGroup>
-      <WorkingDirectory>$(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)</WorkingDirectory>
-    </PropertyGroup>
-
-    <ItemGroup>
-      <InputAssemblies Include="Mono.Cecil.dll" />
-    </ItemGroup>
-    <ILRepack OutputType="$(OutputType)" MainAssembly="$(AssemblyName).dll" OutputAssembly="$(AssemblyName).dll" InputAssemblies="@(InputAssemblies)" WorkingDirectory="$(WorkingDirectory)" />
-    <ItemGroup>
-      <DeleteNonNeededResults Include="$(WorkingDirectory)\*.dll" />
-      <DeleteNonNeededResults Remove="$(WorkingDirectory)\Avalonia.Build.Tasks.dll" />
-    </ItemGroup>
-    <Delete Files="@(DeleteNonNeededResults)" />
-
-  </Target>
 </Project>

+ 1 - 1
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

@@ -5,7 +5,7 @@ using Avalonia.Utilities;
 using Mono.Cecil;
 using Mono.Cecil.Cil;
 using Mono.Collections.Generic;
-using XamlIl.TypeSystem;
+using XamlX.TypeSystem;
 
 namespace Avalonia.Build.Tasks
 {

+ 35 - 25
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -7,17 +7,18 @@ using System.Text;
 using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
 using Microsoft.Build.Framework;
 using Mono.Cecil;
-using XamlIl.TypeSystem;
 using Avalonia.Utilities;
 using Mono.Cecil.Cil;
 using Mono.Cecil.Rocks;
-using XamlIl;
-using XamlIl.Ast;
-using XamlIl.Parsers;
-using XamlIl.Transform;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Parsers;
+using XamlX.Transform;
+using XamlX.TypeSystem;
 using FieldAttributes = Mono.Cecil.FieldAttributes;
 using MethodAttributes = Mono.Cecil.MethodAttributes;
 using TypeAttributes = Mono.Cecil.TypeAttributes;
+using XamlX.IL;
 
 namespace Avalonia.Build.Tasks
 {
@@ -50,23 +51,32 @@ namespace Avalonia.Build.Tasks
             if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
                 // Nothing to do
                 return new CompileResult(true);
-            
-            var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
-            var compilerConfig = new XamlIlTransformerConfiguration(typeSystem,
+
+            var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers",
+                TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+            asm.MainModule.Types.Add(clrPropertiesDef);
+            var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
+                TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+            asm.MainModule.Types.Add(indexerAccessorClosure);
+
+            var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
+            var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
                 typeSystem.TargetAssembly,
                 xamlLanguage,
-                XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
-                AvaloniaXamlIlLanguage.CustomValueConverter);
+                XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
+                AvaloniaXamlIlLanguage.CustomValueConverter,
+                new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
+                new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
 
 
             var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext", 
                 TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
             asm.MainModule.Types.Add(contextDef);
 
-            var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
-                xamlLanguage);
+            var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
+                xamlLanguage, emitConfig);
 
-            var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass) { EnableIlVerification = verifyIl };
+            var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl };
 
             var editorBrowsableAttribute = typeSystem
                 .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
@@ -126,35 +136,35 @@ namespace Avalonia.Build.Tasks
 
                         // StreamReader is needed here to handle BOM
                         var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
-                        var parsed = XDocumentXamlIlParser.Parse(xaml);
+                        var parsed = XDocumentXamlParser.Parse(xaml);
 
-                        var initialRoot = (XamlIlAstObjectNode)parsed.Root;
+                        var initialRoot = (XamlAstObjectNode)parsed.Root;
                         
                         
-                        var precompileDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
+                        var precompileDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
                             .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile");
                         if (precompileDirective != null)
                         {
-                            var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim()
+                            var precompileText = (precompileDirective.Values[0] as XamlAstTextNode)?.Text.Trim()
                                 .ToLowerInvariant();
                             if (precompileText == "false")
                                 continue;
                             if (precompileText != "true")
-                                throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective);
+                                throw new XamlParseException("Invalid value for x:Precompile", precompileDirective);
                         }
                         
-                        var classDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
+                        var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
                             .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
-                        IXamlIlType classType = null;
+                        IXamlType classType = null;
                         if (classDirective != null)
                         {
-                            if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn))
-                                throw new XamlIlParseException("x:Class should have a string value", classDirective);
+                            if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlAstTextNode tn))
+                                throw new XamlParseException("x:Class should have a string value", classDirective);
                             classType = typeSystem.TargetAssembly.FindType(tn.Text);
                             if (classType == null)
-                                throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
+                                throw new XamlParseException($"Unable to find type `{tn.Text}`", classDirective);
                             compiler.OverrideRootType(parsed,
-                                new XamlIlAstClrTypeReference(classDirective, classType, false));
+                                new XamlAstClrTypeReference(classDirective, classType, false));
                             initialRoot.Children.Remove(classDirective);
                         }
                         
@@ -323,7 +333,7 @@ namespace Avalonia.Build.Tasks
                     catch (Exception e)
                     {
                         int lineNumber = 0, linePosition = 0;
-                        if (e is XamlIlParseException xe)
+                        if (e is XamlParseException xe)
                         {
                             lineNumber = xe.LineNumber;
                             linePosition = xe.LinePosition;

+ 8 - 0
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -3920,6 +3920,14 @@ namespace Avalonia.Controls
                     dataGridColumn: CurrentColumn,
                     dataGridRow: EditingRow,
                     dataGridCell: editingCell);
+
+                EditingRow.InvalidateDesiredHeight();
+                var column = editingCell.OwningColumn;
+                if (column.Width.IsSizeToCells || column.Width.IsAuto)
+                {// Invalidate desired width and force recalculation
+                    column.SetWidthDesiredValue(0);
+                    EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width);
+                }
             }
 
             // We're done, so raise the CellEditEnded event

+ 5 - 0
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -767,6 +767,11 @@ namespace Avalonia.Controls
             }
         }
 
+        internal void InvalidateDesiredHeight()
+        {
+            _cellsElement?.InvalidateDesiredHeight();
+        }
+
         internal void ResetGridLine()
         {
             _bottomGridLine = null;

+ 5 - 0
src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs

@@ -299,6 +299,11 @@ namespace Avalonia.Controls.Primitives
             DesiredHeight = 0;
         }
 
+        internal void InvalidateDesiredHeight()
+        {
+            DesiredHeight = 0;
+        }
+
         private bool ShouldDisplayCell(DataGridColumn column, double frozenLeftEdge, double scrollingLeftEdge)
         {
             if (!column.IsVisible)

+ 1 - 1
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@@ -188,7 +188,7 @@
   </Style>
 
   <Style Selector="DataGrid">
-    <Setter Property="RowBackground" Value="{DynamicResource ThemeAccentBrush4}" />
+    <Setter Property="RowBackground" Value="{DynamicResource SystemAccentColorDark2}" />
     <Setter Property="AlternatingRowBackground" Value="#00FFFFFF" />
     <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
     <Setter Property="HeadersVisibility" Value="Column" />

+ 23 - 0
src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs

@@ -0,0 +1,23 @@
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines compensation levels for the platform depending on the transparency level.
+    /// It controls the base opacity level of the 'tracing paper' layer that compensates
+    /// for low blur radius.
+    /// </summary>
+    public struct AcrylicPlatformCompensationLevels
+    {
+        public AcrylicPlatformCompensationLevels(double transparent, double blurred, double acrylic)
+        {
+            TransparentLevel = transparent;
+            BlurLevel = blurred;
+            AcrylicBlurLevel = acrylic;
+        }
+
+        public double TransparentLevel { get; }
+
+        public double BlurLevel { get; }
+
+        public double AcrylicBlurLevel { get; }
+    }
+}

+ 84 - 0
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@@ -0,0 +1,84 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Primitives;
+
+#nullable enable
+
+namespace Avalonia.Controls.Chrome
+{
+    /// <summary>
+    /// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
+    /// </summary>
+    public class CaptionButtons : TemplatedControl
+    {
+        private CompositeDisposable? _disposables;
+        private Window? _hostWindow;
+
+        public void Attach(Window hostWindow)
+        {
+            if (_disposables == null)
+            {
+                _hostWindow = hostWindow;
+
+                _disposables = new CompositeDisposable
+                {
+                    _hostWindow.GetObservable(Window.WindowStateProperty)
+                    .Subscribe(x =>
+                    {
+                        PseudoClasses.Set(":minimized", x == WindowState.Minimized);
+                        PseudoClasses.Set(":normal", x == WindowState.Normal);
+                        PseudoClasses.Set(":maximized", x == WindowState.Maximized);
+                        PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
+                    })
+                };
+            }
+        }
+
+        public void Detach()
+        {
+            if (_disposables != null)
+            {
+                _disposables.Dispose();
+                _disposables = null;
+
+                _hostWindow = null;
+            }
+        }
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+
+            var closeButton = e.NameScope.Get<Panel>("PART_CloseButton");
+            var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton");
+            var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton");
+            var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton");
+
+            closeButton.PointerReleased += (sender, e) => _hostWindow?.Close();
+
+            restoreButton.PointerReleased += (sender, e) =>
+            {
+                if (_hostWindow != null)
+                {
+                    _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
+                }
+            };
+
+            minimiseButton.PointerReleased += (sender, e) =>
+            {
+                if (_hostWindow != null)
+                {
+                    _hostWindow.WindowState = WindowState.Minimized;
+                }
+            };
+
+            fullScreenButton.PointerReleased += (sender, e) =>
+            {
+                if (_hostWindow != null)
+                {
+                    _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen;
+                }
+            };
+        }
+    }
+}

+ 97 - 0
src/Avalonia.Controls/Chrome/TitleBar.cs

@@ -0,0 +1,97 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Primitives;
+
+#nullable enable
+
+namespace Avalonia.Controls.Chrome
+{
+    /// <summary>
+    /// Draws a titlebar when managed client decorations are enabled.
+    /// </summary>
+    public class TitleBar : TemplatedControl
+    {
+        private CompositeDisposable? _disposables;
+        private CaptionButtons? _captionButtons;
+
+        private void UpdateSize(Window window)
+        {
+            if (window != null)
+            {
+                Margin = new Thickness(
+                    window.OffScreenMargin.Left,
+                    window.OffScreenMargin.Top,
+                    window.OffScreenMargin.Right,
+                    window.OffScreenMargin.Bottom);
+
+                if (window.WindowState != WindowState.FullScreen)
+                {
+                    Height = window.WindowDecorationMargin.Top;
+
+                    if (_captionButtons != null)
+                    {
+                        _captionButtons.Height = Height;
+                    }
+                }
+
+                IsVisible = window.PlatformImpl.NeedsManagedDecorations;
+            }
+        }
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+
+            _captionButtons?.Detach();
+            
+            _captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons");
+
+            if (VisualRoot is Window window)
+            {
+                _captionButtons?.Attach(window);   
+                
+                UpdateSize(window);
+            }
+        }
+
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+
+            if (VisualRoot is Window window)
+            {
+                _disposables = new CompositeDisposable
+                {
+                    window.GetObservable(Window.WindowDecorationMarginProperty)
+                        .Subscribe(x => UpdateSize(window)),
+                    window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
+                        .Subscribe(x => UpdateSize(window)),
+                    window.GetObservable(Window.OffScreenMarginProperty)
+                        .Subscribe(x => UpdateSize(window)),
+                    window.GetObservable(Window.ExtendClientAreaChromeHintsProperty)
+                        .Subscribe(x => UpdateSize(window)),
+                    window.GetObservable(Window.WindowStateProperty)
+                        .Subscribe(x =>
+                        {
+                            PseudoClasses.Set(":minimized", x == WindowState.Minimized);
+                            PseudoClasses.Set(":normal", x == WindowState.Normal);
+                            PseudoClasses.Set(":maximized", x == WindowState.Maximized);
+                            PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
+                        }),
+                    window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty)
+                        .Subscribe(x => UpdateSize(window))
+                };
+            }
+        }
+
+        protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromVisualTree(e);
+
+            _disposables?.Dispose();
+            
+            _captionButtons?.Detach();
+            _captionButtons = null;
+        }
+    }
+}

+ 31 - 0
src/Avalonia.Controls/ComboBox.cs

@@ -7,6 +7,7 @@ using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Interactivity;
+using Avalonia.Layout;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.VisualTree;
@@ -63,6 +64,18 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IBrush> PlaceholderForegroundProperty =
             AvaloniaProperty.Register<ComboBox, IBrush>(nameof(PlaceholderForeground));
 
+        /// <summary>
+        /// Defines the <see cref="HorizontalContentAlignment"/> property.
+        /// </summary>
+        public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
+            ContentControl.HorizontalContentAlignmentProperty.AddOwner<ComboBox>();
+
+        /// <summary>
+        /// Defines the <see cref="VerticalContentAlignment"/> property.
+        /// </summary>
+        public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
+            ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
+
         private bool _isDropDownOpen;
         private Popup _popup;
         private object _selectionBoxItem;
@@ -133,6 +146,24 @@ namespace Avalonia.Controls
             set { SetValue(VirtualizationModeProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the horizontal alignment of the content within the control.
+        /// </summary>
+        public HorizontalAlignment HorizontalContentAlignment
+        {
+            get { return GetValue(HorizontalContentAlignmentProperty); }
+            set { SetValue(HorizontalContentAlignmentProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the vertical alignment of the content within the control.
+        /// </summary>
+        public VerticalAlignment VerticalContentAlignment
+        {
+            get { return GetValue(VerticalContentAlignmentProperty); }
+            set { SetValue(VerticalContentAlignmentProperty, value); }
+        }
+
         /// <inheritdoc/>
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         {

+ 10 - 6
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@@ -88,7 +88,7 @@ namespace Avalonia.Controls
             AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), 
                 x => x.SelectedDate, (x, v) => x.SelectedDate = v);
 
-        //Template Items
+        // Template Items
         private Button _flyoutButton;
         private TextBlock _dayText;
         private TextBlock _monthText;
@@ -359,10 +359,14 @@ namespace Avalonia.Controls
                 }
             }
 
-            Grid.SetColumn(_spacer1, 1);
-            Grid.SetColumn(_spacer2, 3);
-            _spacer1.IsVisible = columnIndex > 1;
-            _spacer2.IsVisible = columnIndex > 2;
+            var isSpacer1Visible = columnIndex > 1;
+            var isSpacer2Visible = columnIndex > 2;
+            // ternary conditional operator is used to make sure grid cells will be validated
+            Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
+            Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+
+            _spacer1.IsVisible = isSpacer1Visible;
+            _spacer2.IsVisible = isSpacer2Visible;
         }
 
         private void SetSelectedDateText()
@@ -398,7 +402,7 @@ namespace Avalonia.Controls
 
             var deltaY = _presenter.GetOffsetForPopup();
 
-            //The extra 5 px I think is related to default popup placement behavior
+            // The extra 5 px I think is related to default popup placement behavior
             _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
                 Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
                  Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);

+ 17 - 14
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@@ -77,7 +77,7 @@ namespace Avalonia.Controls
             DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => 
             x.YearVisible, (x, v) => x.YearVisible = v);
 
-        //Template Items
+        // Template Items
         private Grid _pickerContainer;
         private Button _acceptButton;
         private Button _dismissButton;
@@ -107,7 +107,7 @@ namespace Avalonia.Controls
         private bool _yearVisible = true;
         private DateTimeOffset _syncDate;
 
-        private GregorianCalendar _calendar;
+        private readonly GregorianCalendar _calendar;
         private bool _suppressUpdateSelection;
 
         public DatePickerPresenter()
@@ -234,7 +234,7 @@ namespace Avalonia.Controls
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             base.OnApplyTemplate(e);
-            //These are requirements, so throw if not found
+            // These are requirements, so throw if not found
             _pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
             _monthHost = e.NameScope.Get<Panel>("MonthHost");
             _dayHost = e.NameScope.Get<Panel>("DayHost");
@@ -326,7 +326,7 @@ namespace Avalonia.Controls
         /// </summary>
         private void InitPicker()
         {
-            //OnApplyTemplate must've been called before we can init here...
+            // OnApplyTemplate must've been called before we can init here...
             if (_pickerContainer == null)
                 return;
 
@@ -344,12 +344,11 @@ namespace Avalonia.Controls
 
             SetGrid();
 
-            //Date should've been set when we reach this point
+            // Date should've been set when we reach this point
             var dt = Date;
             if (DayVisible)
             {
-                GregorianCalendar gc = new GregorianCalendar();
-                var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month);
+                var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month);
                 _daySelector.MaximumValue = maxDays;
                 _daySelector.MinimumValue = 1;
                 _daySelector.SelectedValue = dt.Day;
@@ -407,10 +406,14 @@ namespace Avalonia.Controls
                 }
             }
 
-            Grid.SetColumn(_spacer1, 1);
-            Grid.SetColumn(_spacer2, 3);
-            _spacer1.IsVisible = columnIndex > 1;
-            _spacer2.IsVisible = columnIndex > 2;
+            var isSpacer1Visible = columnIndex > 1;
+            var isSpacer2Visible = columnIndex > 2;
+            // ternary conditional operator is used to make sure grid cells will be validated
+            Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
+            Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+
+            _spacer1.IsVisible = isSpacer1Visible;
+            _spacer2.IsVisible = isSpacer2Visible;
         }
 
         private void SetInitialFocus()
@@ -433,12 +436,12 @@ namespace Avalonia.Controls
             }
         }
 
-        private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
         {
             OnDismiss();
         }
 
-        private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
         {
             Date = _syncDate;
             OnConfirmed();
@@ -471,7 +474,7 @@ namespace Avalonia.Controls
 
             _syncDate = newDate;
 
-            //We don't need to update the days if not displaying day, not february
+            // We don't need to update the days if not displaying day, not february
             if (!DayVisible || _syncDate.Month != 2)
                 return;
 

+ 18 - 33
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="MinuteIncrement"/> property
         /// </summary>
         public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty =
-            AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), 
+            AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement),
                 x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
 
         /// <summary>
@@ -34,17 +34,17 @@ namespace Avalonia.Controls
         /// Defines the <see cref="ClockIdentifier"/> property
         /// </summary>
         public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty =
-           AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier), 
+           AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier),
                x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v);
 
         /// <summary>
         /// Defines the <see cref="SelectedTime"/> property
         /// </summary>
         public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty =
-            AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime), 
+            AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime),
                 x => x.SelectedTime, (x, v) => x.SelectedTime = v);
 
-        //Template Items
+        // Template Items
         private TimePickerPresenter _presenter;
         private Button _flyoutButton;
         private Border _firstPickerHost;
@@ -52,7 +52,7 @@ namespace Avalonia.Controls
         private Border _thirdPickerHost;
         private TextBlock _hourText;
         private TextBlock _minuteText;
-        public TextBlock _periodText;
+        private TextBlock _periodText;
         private Rectangle _firstSplitter;
         private Rectangle _secondSplitter;
         private Grid _contentGrid;
@@ -145,7 +145,7 @@ namespace Avalonia.Controls
             if (_flyoutButton != null)
                 _flyoutButton.Click -= OnFlyoutButtonClicked;
 
-            if(_presenter != null)
+            if (_presenter != null)
             {
                 _presenter.Confirmed -= OnConfirmed;
                 _presenter.Dismissed -= OnDismissPicker;
@@ -170,7 +170,6 @@ namespace Avalonia.Controls
             _popup = e.NameScope.Find<Popup>("Popup");
             _presenter = e.NameScope.Find<TimePickerPresenter>("PickerPresenter");
 
-
             if (_flyoutButton != null)
                 _flyoutButton.Click += OnFlyoutButtonClicked;
 
@@ -185,7 +184,6 @@ namespace Avalonia.Controls
                 _presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty];
                 _presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty];
             }
-
         }
 
         private void SetGrid()
@@ -195,30 +193,19 @@ namespace Avalonia.Controls
 
             bool use24HourClock = ClockIdentifier == "24HourClock";
 
-            if (!use24HourClock)
-            {
-                _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
-                _thirdPickerHost.IsVisible = true;
-                _secondSplitter.IsVisible = true;
+            var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
+            _contentGrid.ColumnDefinitions = new ColumnDefinitions(columnsD);
 
-                Grid.SetColumn(_firstPickerHost, 0);
-                Grid.SetColumn(_secondPickerHost, 2);
-                Grid.SetColumn(_thirdPickerHost, 4);
+            _thirdPickerHost.IsVisible = !use24HourClock;
+            _secondSplitter.IsVisible = !use24HourClock;
 
-                Grid.SetColumn(_firstSplitter, 1);
-                Grid.SetColumn(_secondSplitter, 3);
-            }
-            else
-            {
-                _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
-                _thirdPickerHost.IsVisible = false;
-                _secondSplitter.IsVisible = false;
+            Grid.SetColumn(_firstPickerHost, 0);
+            Grid.SetColumn(_secondPickerHost, 2);
 
-                Grid.SetColumn(_firstPickerHost, 0);
-                Grid.SetColumn(_secondPickerHost, 2);
+            Grid.SetColumn(_thirdPickerHost, use24HourClock ? 0 : 4);
 
-                Grid.SetColumn(_firstSplitter, 1);
-            }
+            Grid.SetColumn(_firstSplitter, 1);
+            Grid.SetColumn(_secondSplitter, use24HourClock ? 0 : 3);
         }
 
         private void SetSelectedTimeText()
@@ -237,14 +224,13 @@ namespace Avalonia.Controls
                     hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
                     newTime = new TimeSpan(hr, newTime.Minutes, 0);
                 }
-                _hourText.Text = newTime.ToString("%h");
 
+                _hourText.Text = newTime.ToString("%h");
                 _minuteText.Text = newTime.ToString("mm");
                 PseudoClasses.Set(":hasnotime", false);
 
                 _periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator :
                     CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
-
             }
             else
             {
@@ -262,7 +248,7 @@ namespace Avalonia.Controls
             SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime));
         }
 
-        private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void OnFlyoutButtonClicked(object sender, Interactivity.RoutedEventArgs e)
         {
             _presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
 
@@ -270,7 +256,7 @@ namespace Avalonia.Controls
 
             var deltaY = _presenter.GetOffsetForPopup();
 
-            //The extra 5 px I think is related to default popup placement behavior
+            // The extra 5 px I think is related to default popup placement behavior
             _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
                 Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
                  Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
@@ -287,6 +273,5 @@ namespace Avalonia.Controls
             _popup.Close();
             SelectedTime = _presenter.Time;
         }
-
     }
 }

+ 18 - 20
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@@ -39,7 +39,7 @@ namespace Avalonia.Controls
             KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
         }
 
-        //TemplateItems
+        // TemplateItems
         private Grid _pickerContainer;
         private Button _acceptButton;
         private Button _dismissButton;
@@ -55,8 +55,8 @@ namespace Avalonia.Controls
         private Button _minuteDownButton;
         private Button _periodDownButton;
 
-        //Backing Fields
-        private TimeSpan _Time;
+        // Backing Fields
+        private TimeSpan _time;
         private int _minuteIncrement = 1;
         private string _clockIdentifier = "12HourClock";
 
@@ -83,7 +83,7 @@ namespace Avalonia.Controls
             get => _clockIdentifier;
             set
             {
-                if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock"))
+                if (string.IsNullOrEmpty(value) || !(value == "12HourClock" || value == "24HourClock"))
                     throw new ArgumentException("Invalid ClockIdentifier");
                 SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
                 InitPicker();
@@ -95,10 +95,10 @@ namespace Avalonia.Controls
         /// </summary>
         public TimeSpan Time
         {
-            get => _Time;
+            get => _time;
             set
             {
-                SetAndRaise(TimeProperty, ref _Time, value);
+                SetAndRaise(TimeProperty, ref _time, value);
                 InitPicker();
             }
         }
@@ -213,26 +213,24 @@ namespace Avalonia.Controls
 
         private void SetGrid()
         {
-            if (ClockIdentifier == "12HourClock")
-            {
-                _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
-                _spacer2.IsVisible = true;
-                _periodHost.IsVisible = true;
-            }
-            else
-            {
-                _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
-                _spacer2.IsVisible = false;
-                _periodHost.IsVisible = false;
-            }
+            bool use24HourClock = ClockIdentifier == "24HourClock";
+
+            var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
+            _pickerContainer.ColumnDefinitions = new ColumnDefinitions(columnsD);
+
+            _spacer2.IsVisible = !use24HourClock;
+            _periodHost.IsVisible = !use24HourClock;
+
+            Grid.SetColumn(_spacer2, use24HourClock ? 0 : 3);
+            Grid.SetColumn(_periodHost, use24HourClock ? 0 : 4);
         }
 
-        private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
         {
             OnDismiss();
         }
 
-        private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
         {
             OnConfirmed();
         }

+ 4 - 1
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
             }
         }
 
-        public double Scaling
+        public double RenderScaling
         {
             get { return _scaling; }
             set
@@ -52,6 +52,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
 
         public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
 
+        /// <inheritdoc/>
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+
         public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
 
         public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1);

+ 117 - 0
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@@ -0,0 +1,117 @@
+using Avalonia.Controls.Utils;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Avalonia.Platform;
+using System;
+
+namespace Avalonia.Controls
+{
+    public class ExperimentalAcrylicBorder : Decorator
+    {
+        public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
+            Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>();
+
+        public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial>(nameof(Material));
+
+        private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
+
+        private IDisposable _subscription;
+
+        static ExperimentalAcrylicBorder()
+        {
+            AffectsRender<ExperimentalAcrylicBorder>(
+                MaterialProperty,
+                CornerRadiusProperty);
+        }
+
+
+        /// <summary>
+        /// Gets or sets the radius of the border rounded corners.
+        /// </summary>
+        public CornerRadius CornerRadius
+        {
+            get { return GetValue(CornerRadiusProperty); }
+            set { SetValue(CornerRadiusProperty, value); }
+        }
+
+        public ExperimentalAcrylicMaterial Material
+        {
+            get => GetValue(MaterialProperty);
+            set => SetValue(MaterialProperty, value);
+        }
+
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+
+            var tl = (e.Root as TopLevel);
+
+            _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
+                .Subscribe(x =>
+                {
+                    switch (x)
+                    {
+                        case WindowTransparencyLevel.Transparent:
+                        case WindowTransparencyLevel.None:
+                            Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
+                            break;
+
+                        case WindowTransparencyLevel.Blur:
+                            Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
+                            break;
+
+                        case WindowTransparencyLevel.AcrylicBlur:
+                            Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
+                            break;
+                    }
+                });
+        }
+
+        protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromVisualTree(e);
+
+            _subscription?.Dispose();
+        }
+
+        public override void Render(DrawingContext context)
+        {
+            if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc)
+            {
+                var cornerRadius = CornerRadius;
+
+                idc.DrawRectangle(
+                    Material,
+                    new RoundedRect(
+                        new Rect(Bounds.Size),
+                        cornerRadius.TopLeft, cornerRadius.TopRight,
+                        cornerRadius.BottomRight, cornerRadius.BottomLeft));
+            }
+            else
+            {
+                _borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new SolidColorBrush(Material.FallbackColor), null, default);
+            }
+        }
+
+        /// <summary>
+        /// Measures the control.
+        /// </summary>
+        /// <param name="availableSize">The available size.</param>
+        /// <returns>The desired size of the control.</returns>
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            return LayoutHelper.MeasureChild(Child, availableSize, Padding);
+        }
+
+        /// <summary>
+        /// Arranges the control's child.
+        /// </summary>
+        /// <param name="finalSize">The size allocated to the control.</param>
+        /// <returns>The space taken.</returns>
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            return LayoutHelper.ArrangeChild(Child, finalSize, Padding);
+        }
+    }
+}

+ 0 - 1
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators
             private readonly IDataTemplate _inner;
             public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
             public IControl Build(object param) => _inner.Build(param);
-            public bool SupportsRecycling => _inner.SupportsRecycling;
             public bool Match(object data) => _inner.Match(data);
             public InstancedBinding ItemsSelector(object item) => null;
         }

+ 38 - 0
src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs

@@ -0,0 +1,38 @@
+using System;
+
+namespace Avalonia.Platform
+{
+    /// <summary>
+    /// Hint for Window Chrome when ClientArea is Extended.
+    /// </summary>
+    [Flags]
+    public enum ExtendClientAreaChromeHints
+    {
+        /// <summary>
+        /// The will be no chrome at all.
+        /// </summary>
+        NoChrome,
+
+        /// <summary>
+        /// The default for the platform.
+        /// </summary>
+        Default = SystemChrome,
+
+        /// <summary>
+        /// Use SystemChrome
+        /// </summary>
+        SystemChrome = 0x01,
+
+        /// <summary>
+        /// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
+        /// This is because Windows Chrome can not be shown ontop of user content.
+        /// </summary>
+        PreferSystemChrome = 0x02,
+
+        /// <summary>
+        /// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
+        /// slightly lower than normal.
+        /// </summary>
+        OSXThickTitleBar = 0x08,               
+    }
+}

+ 8 - 3
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -23,10 +23,10 @@ namespace Avalonia.Platform
         Size ClientSize { get; }
 
         /// <summary>
-        /// Gets the scaling factor for the toplevel.
+        /// Gets the scaling factor for the toplevel. This is used for rendering.
         /// </summary>
-        double Scaling { get; }
-
+        double RenderScaling { get; }
+        
         /// <summary>
         /// The list of native platform's surfaces that can be consumed by rendering subsystems.
         /// </summary>
@@ -127,5 +127,10 @@ namespace Avalonia.Platform
         /// Gets the current <see cref="WindowTransparencyLevel"/> of the TopLevel.
         /// </summary>
         WindowTransparencyLevel TransparencyLevel { get; }
+
+        /// <summary>
+        /// Gets the <see cref="AcrylicPlatformCompensationLevels"/> for the platform.        
+        /// </summary>
+        AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
     }
 }

+ 5 - 0
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@@ -13,6 +13,11 @@ namespace Avalonia.Platform
         /// Hides the window.
         /// </summary>
         void Hide();
+        
+        /// <summary>
+        /// Gets the scaling factor for Window positioning and sizing.
+        /// </summary>
+        double DesktopScaling { get; }
 
         /// <summary>
         /// Gets the position of the window in device pixels.

+ 46 - 0
src/Avalonia.Controls/Platform/IWindowImpl.cs

@@ -68,6 +68,34 @@ namespace Avalonia.Platform
         /// </summary>
         Func<bool> Closing { get; set; }
 
+        /// <summary>
+        /// Gets a value to indicate if the platform was able to extend client area to non-client area.
+        /// </summary>
+        bool IsClientAreaExtendedToDecorations { get; }
+
+        /// <summary>
+        /// Gets or Sets an action that is called whenever one of the extend client area properties changed.
+        /// </summary>
+        Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
+
+        /// <summary>
+        /// Gets a flag that indicates if Managed decorations i.e. caption buttons are required.
+        /// This property is used when <see cref="IsClientAreaExtendedToDecorations"/> is set.
+        /// </summary>
+        bool NeedsManagedDecorations { get; }
+
+        /// <summary>
+        /// Gets a thickness that describes the amount each side of the non-client area extends into the client area.
+        /// It includes the titlebar.
+        /// </summary>
+        Thickness ExtendedMargins { get; }
+
+        /// <summary>
+        /// Gets a thickness that describes the margin around the window that is offscreen.
+        /// This may happen when a window is maximized and <see cref="IsClientAreaExtendedToDecorations"/> is set.
+        /// </summary>
+        Thickness OffScreenMargin { get; }
+
         /// <summary>
         /// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
         /// </summary>
@@ -94,5 +122,23 @@ namespace Avalonia.Platform
         /// </summary>
         /// 
         void SetMinMaxSize(Size minSize, Size maxSize);
+
+        /// <summary>
+        /// Sets if the ClientArea is extended into the non-client area.
+        /// </summary>
+        /// <param name="extendIntoClientAreaHint">true to enable, false to disable</param>
+        void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint);        
+
+        /// <summary>
+        /// Sets hints that configure how the client area extends. 
+        /// </summary>
+        /// <param name="hints"></param>
+        void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints);
+
+        /// <summary>
+        /// Sets how big the non-client titlebar area should be.
+        /// </summary>
+        /// <param name="titleBarHeight">-1 for platform default, otherwise the height in DIPs.</param>
+        void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight);       
     }
 }

+ 5 - 0
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@@ -155,6 +155,11 @@ namespace Avalonia.Controls.Presenters
             }
         }
 
+        protected override void PanelCreated(IPanel panel)
+        {
+            ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+        }
+
         /// <summary>
         /// Moves to the selected page, animating if a <see cref="PageTransition"/> is set.
         /// </summary>

+ 11 - 11
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters
 
         private IControl _child;
         private bool _createdChild;
-        private IDataTemplate _dataTemplate;
+        private IRecyclingDataTemplate _recyclingDataTemplate;
         private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
 
         /// <summary>
@@ -95,6 +95,7 @@ namespace Avalonia.Controls.Presenters
         static ContentPresenter()
         {
             AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
+            AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
             AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
             ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
             ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
@@ -280,7 +281,7 @@ namespace Avalonia.Controls.Presenters
         protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
             base.OnAttachedToLogicalTree(e);
-            _dataTemplate = null;
+            _recyclingDataTemplate = null;
             _createdChild = false;
             InvalidateMeasure();
         }
@@ -306,22 +307,21 @@ namespace Avalonia.Controls.Presenters
             {
                 var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
 
-                // We have content and it isn't a control, so if the new data template is the same
-                // as the old data template, try to recycle the existing child control to display
-                // the new data.
-                if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
+                if (dataTemplate is IRecyclingDataTemplate rdt)
                 {
-                    newChild = oldChild;
+                    var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null;
+                    newChild = rdt.Build(content, toRecycle);
+                    _recyclingDataTemplate = rdt;
                 }
                 else
                 {
-                    _dataTemplate = dataTemplate;
-                    newChild = _dataTemplate.Build(content);
+                    newChild = dataTemplate.Build(content);
+                    _recyclingDataTemplate = null;
                 }
             }
             else
             {
-                _dataTemplate = null;
+                _recyclingDataTemplate = null;
             }
 
             return newChild;
@@ -421,7 +421,7 @@ namespace Avalonia.Controls.Presenters
                 LogicalChildren.Remove(Child);
                 ((ISetInheritanceParent)Child).SetParent(Child.Parent);
                 Child = null;
-                _dataTemplate = null;
+                _recyclingDataTemplate = null;
             }
 
             InvalidateMeasure();

+ 0 - 2
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@@ -229,8 +229,6 @@ namespace Avalonia.Controls.Presenters
             }
 
             PanelCreated(Panel);
-
-            ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
         }
 
         /// <summary>

+ 2 - 2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -273,7 +273,7 @@ namespace Avalonia.Controls.Presenters
             return new FormattedText
             {
                 Constraint = constraint,
-                Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
+                Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
                 FontSize = FontSize,
                 Text = text ?? string.Empty,
                 TextAlignment = TextAlignment,
@@ -490,7 +490,7 @@ namespace Avalonia.Controls.Presenters
                 return new FormattedText
                 {
                     Text = "X",
-                    Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
+                    Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight),
                     FontSize = FontSize,
                     TextAlignment = TextAlignment,
                     Constraint = availableSize,

+ 10 - 11
src/Avalonia.Controls/Primitives/AccessText.cs

@@ -97,9 +97,7 @@ namespace Avalonia.Controls.Primitives
             {
                 var lastLine = TextLayout.TextLines[TextLayout.TextLines.Count - 1];
 
-                var offsetX = lastLine.LineMetrics.BaselineOrigin.X;
-
-                var lineX = offsetX + lastLine.LineMetrics.Size.Width;
+                var lineX = lastLine.LineMetrics.Size.Width;
 
                 var lineY = Bounds.Height - lastLine.LineMetrics.Size.Height;
 
@@ -110,32 +108,33 @@ namespace Avalonia.Controls.Primitives
 
             foreach (var textLine in TextLayout.TextLines)
             {
-                if (textLine.Text.End < textPosition)
+                if (textLine.TextRange.End < textPosition)
                 {
                     currentY += textLine.LineMetrics.Size.Height;
 
                     continue;
                 }
 
-                var currentX = textLine.LineMetrics.BaselineOrigin.X;
+                var currentX = 0.0;
 
                 foreach (var textRun in textLine.TextRuns)
                 {
-                    if (!(textRun is ShapedTextRun shapedRun))
+                    if (!(textRun is ShapedTextCharacters shapedTextCharacters))
                     {
                         continue;
                     }
 
-                    if (shapedRun.GlyphRun.Characters.End < textPosition)
+                    if (shapedTextCharacters.GlyphRun.Characters.End < textPosition)
                     {
-                        currentX += shapedRun.GlyphRun.Bounds.Width;
+                        currentX += shapedTextCharacters.GlyphRun.Bounds.Width;
 
                         continue;
                     }
 
-                    var characterHit = shapedRun.GlyphRun.FindNearestCharacterHit(textPosition, out var width);
+                    var characterHit =
+                        shapedTextCharacters.GlyphRun.FindNearestCharacterHit(textPosition, out var width);
 
-                    var distance = shapedRun.GlyphRun.GetDistanceFromCharacterHit(characterHit);
+                    var distance = shapedTextCharacters.GlyphRun.GetDistanceFromCharacterHit(characterHit);
 
                     currentX += distance - width;
 
@@ -144,7 +143,7 @@ namespace Avalonia.Controls.Primitives
                         width = 0.0;
                     }
 
-                    return new Rect(currentX, currentY, width, shapedRun.GlyphRun.Bounds.Height);
+                    return new Rect(currentX, currentY, width, shapedTextCharacters.GlyphRun.Bounds.Height);
                 }
             }
 

+ 34 - 0
src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs

@@ -0,0 +1,34 @@
+using System.Linq;
+using Avalonia.Rendering;
+using Avalonia.VisualTree;
+
+#nullable enable
+
+namespace Avalonia.Controls.Primitives
+{
+    public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest
+    {
+        public static Panel? GetOverlayLayer(IVisual visual)
+        {
+            foreach (var v in visual.GetVisualAncestors())
+                if (v is VisualLayerManager vlm)
+                    if (vlm.OverlayLayer != null)
+                        return vlm.ChromeOverlayLayer;
+
+            if (visual is TopLevel tl)
+            {
+                var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
+                return layers?.ChromeOverlayLayer;
+            }
+
+            return null;
+        }
+
+        public void Add(Control c)
+        {
+            base.Children.Add(c);
+        }
+
+        public bool HitTest(Point point) => Children.HitTestCustom(point);
+    }
+}

+ 2 - 2
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs

@@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
 
         public void MoveAndResize(Point devicePoint, Size virtualSize)
         {
-            _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
+            _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.RenderScaling);
         }
 
-        public virtual double Scaling => _parent.Scaling;
+        public virtual double Scaling => _parent.DesktopScaling;
     }
 }

+ 15 - 5
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives
                 }
             }
 
-            foreach (var i in e.SelectedIndices)
+            if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0)
             {
-                Mark(i.GetAt(0), true);
-            }
+                foreach (var i in e.SelectedIndices)
+                {
+                    Mark(i.GetAt(0), true);
+                }
 
-            foreach (var i in e.DeselectedIndices)
+                foreach (var i in e.DeselectedIndices)
+                {
+                    Mark(i.GetAt(0), false);
+                }
+            }
+            else if (e.DeselectedItems.Count > 0)
             {
-                Mark(i.GetAt(0), false);
+                // (De)selected indices being empty means that a selected item was removed from
+                // the Items (it can't tell us the index of the item because the index is no longer
+                // valid). In this case, we just update the selection state of all containers.
+                UpdateContainerSelection();
             }
 
             var newSelectedIndex = SelectedIndex;

+ 9 - 0
src/Avalonia.Controls/Primitives/Track.cs

@@ -353,6 +353,15 @@ namespace Avalonia.Controls.Primitives
             var trackLength = isVertical ? arrangeSize.Height : arrangeSize.Width;
             double thumbMinLength = 10;
 
+            StyledProperty<double> minLengthProperty = isVertical ? MinHeightProperty : MinWidthProperty;
+
+            var thumb = Thumb;
+
+            if (thumb != null && thumb.IsSet(minLengthProperty))
+            {
+                thumbMinLength = thumb.GetValue(minLengthProperty);
+            }
+
             thumbLength = trackLength * viewportSize / extent;
             CoerceLength(ref thumbLength, trackLength);
             thumbLength = Math.Max(thumbMinLength, thumbLength);

+ 31 - 5
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@@ -1,18 +1,23 @@
 using System.Collections.Generic;
 using Avalonia.LogicalTree;
+using Avalonia.Metadata;
 
 namespace Avalonia.Controls.Primitives
 {
     public class VisualLayerManager : Decorator
     {
         private const int AdornerZIndex = int.MaxValue - 100;
-        private const int OverlayZIndex = int.MaxValue - 99;
+        private const int ChromeZIndex = int.MaxValue - 99;
+        private const int OverlayZIndex = int.MaxValue - 98;
+
         private ILogicalRoot _logicalRoot;
         private readonly List<Control> _layers = new List<Control>();
-        
+
+        public static readonly StyledProperty<ChromeOverlayLayer> ChromeOverlayLayerProperty =
+            AvaloniaProperty.Register<VisualLayerManager, ChromeOverlayLayer>(nameof(ChromeOverlayLayer));
 
         public bool IsPopup { get; set; }
-        
+
         public AdornerLayer AdornerLayer
         {
             get
@@ -24,6 +29,26 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        public ChromeOverlayLayer ChromeOverlayLayer
+        {
+            get
+            {
+                var current = GetValue(ChromeOverlayLayerProperty);
+
+                if (current is null)
+                {
+                    var chromeOverlayLayer = new ChromeOverlayLayer();
+                    AddLayer(chromeOverlayLayer, ChromeZIndex);
+
+                    SetValue(ChromeOverlayLayerProperty, chromeOverlayLayer);
+
+                    current = chromeOverlayLayer;
+                }
+
+                return current;
+            }
+        }
+
         public OverlayLayer OverlayLayer
         {
             get
@@ -31,7 +56,7 @@ namespace Avalonia.Controls.Primitives
                 if (IsPopup)
                     return null;
                 var rv = FindLayer<OverlayLayer>();
-                if(rv == null)
+                if (rv == null)
                     AddLayer(rv = new OverlayLayer(), OverlayZIndex);
                 return rv;
             }
@@ -52,7 +77,8 @@ namespace Avalonia.Controls.Primitives
             layer.ZIndex = zindex;
             VisualChildren.Add(layer);
             if (((ILogical)this).IsAttachedToLogicalTree)
-                ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
+                ((ILogical)layer).NotifyAttachedToLogicalTree(
+                    new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
             InvalidateArrange();
         }
 

+ 1 - 0
src/Avalonia.Controls/Properties/AssemblyInfo.cs

@@ -13,3 +13,4 @@ using Avalonia.Metadata;
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Shapes")]
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Templates")]
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Notifications")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Chrome")]

+ 546 - 0
src/Avalonia.Controls/RelativePanel.AttachedProperties.cs

@@ -0,0 +1,546 @@
+using Avalonia.Layout;
+
+#nullable enable
+
+namespace Avalonia.Controls
+{
+    public partial class RelativePanel
+    {
+        private static void OnAlignPropertiesChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (d is Layoutable layoutable && layoutable.Parent is Layoutable layoutableParent)
+            {
+                layoutableParent.InvalidateArrange();
+            }
+        }
+
+        static RelativePanel()
+        {
+            ClipToBoundsProperty.OverrideDefaultValue<RelativePanel>(true);
+
+            AboveProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignBottomWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignBottomWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignHorizontalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignHorizontalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignLeftWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignLeftWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignRightWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignRightWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignTopWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignTopWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignVerticalCenterWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            AlignVerticalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            BelowProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            LeftOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            RightOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+        }
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.Above XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.Above XAML attached property value of the specified object.
+        /// (The element to position this element above.)
+        /// </returns>        
+        [ResolveByName]
+        public static object GetAbove(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(AboveProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to position this element above.)</param>
+        public static void SetAbove(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(AboveProperty, value);
+        }
+
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AboveProperty"/> XAML attached property.
+        /// </summary>        
+
+        public static readonly AttachedProperty<object> AboveProperty =
+            AvaloniaProperty.RegisterAttached<Layoutable, object>("Above", typeof(RelativePanel));
+
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignBottomWithPanel XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignBottomWithPanel XAML attached property value of the specified
+        ///    object. (true to align this element's bottom edge with the panel's bottom edge;
+        /// otherwise, false.)
+        /// </returns>
+        public static bool GetAlignBottomWithPanel(AvaloniaObject obj)
+        {
+            return (bool)obj.GetValue(AlignBottomWithPanelProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">
+        /// The value to set. (true to align this element's bottom edge with the panel's
+        /// bottom edge; otherwise, false.)
+        /// </param>
+        public static void SetAlignBottomWithPanel(AvaloniaObject obj, bool value)
+        {
+            obj.SetValue(AlignBottomWithPanelProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignBottomWithPanelProperty"/> XAML attached property.
+        /// </summary>
+        public static readonly AttachedProperty<bool> AlignBottomWithPanelProperty =
+            AvaloniaProperty.RegisterAttached<Layoutable, bool>("AlignBottomWithPanel", typeof(RelativePanel));
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignBottomWith XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignBottomWith XAML attached property value of the specified object.
+        /// (The element to align this element's bottom edge with.)
+        /// </returns>        
+        [ResolveByName]
+        public static object GetAlignBottomWith(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(AlignBottomWithProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to align this element's bottom edge with.)</param>
+        public static void SetAlignBottomWith(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(AlignBottomWithProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignBottomWithProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> AlignBottomWithProperty =
+            AvaloniaProperty.RegisterAttached<Layoutable, object>("AlignBottomWith", typeof(RelativePanel));
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignHorizontalCenterWithPanel XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignHorizontalCenterWithPanel XAML attached property value
+        /// of the specified object. (true to horizontally center this element in the panel;
+        /// otherwise, false.)
+        /// </returns>
+        public static bool GetAlignHorizontalCenterWithPanel(AvaloniaObject obj)
+        {
+            return (bool)obj.GetValue(AlignHorizontalCenterWithPanelProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">
+        /// The value to set. (true to horizontally center this element in the panel; otherwise,
+        /// false.)
+        /// </param>
+        public static void SetAlignHorizontalCenterWithPanel(AvaloniaObject obj, bool value)
+        {
+            obj.SetValue(AlignHorizontalCenterWithPanelProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignHorizontalCenterWithPanelProperty"/> XAML attached property.
+        /// </summary>
+        public static readonly AttachedProperty<bool> AlignHorizontalCenterWithPanelProperty =
+            AvaloniaProperty.RegisterAttached<Layoutable, bool>("AlignHorizontalCenterWithPanel", typeof(RelativePanel), false);
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignHorizontalCenterWith XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignHorizontalCenterWith XAML attached property value of the
+        /// specified object. (The element to align this element's horizontal center with.)
+        /// </returns>        
+        [ResolveByName]
+        public static object GetAlignHorizontalCenterWith(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(AlignHorizontalCenterWithProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to align this element's horizontal center with.)</param>
+        public static void SetAlignHorizontalCenterWith(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(AlignHorizontalCenterWithProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignHorizontalCenterWithProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> AlignHorizontalCenterWithProperty =
+            AvaloniaProperty.RegisterAttached<Layoutable, object>("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel));
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignLeftWithPanel XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignLeftWithPanel XAML attached property value of the specified
+        /// object. (true to align this element's left edge with the panel's left edge; otherwise,
+        /// false.)
+        /// </returns>
+        public static bool GetAlignLeftWithPanel(AvaloniaObject obj)
+        {
+            return (bool)obj.GetValue(AlignLeftWithPanelProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">
+        ///  The value to set. (true to align this element's left edge with the panel's left
+        ///  edge; otherwise, false.)
+        /// </param>
+        public static void SetAlignLeftWithPanel(AvaloniaObject obj, bool value)
+        {
+            obj.SetValue(AlignLeftWithPanelProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignLeftWithPanelProperty"/> XAML attached property.
+        /// </summary>
+        public static readonly AttachedProperty<bool> AlignLeftWithPanelProperty =
+            AvaloniaProperty.RegisterAttached<Layoutable, bool>("AlignLeftWithPanel", typeof(RelativePanel), false);
+
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignLeftWith XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignLeftWith XAML attached property value of the specified
+        /// object. (The element to align this element's left edge with.)
+        /// </returns>        
+        [ResolveByName]
+        public static object GetAlignLeftWith(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(AlignLeftWithProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to align this element's left edge with.)</param>
+        public static void SetAlignLeftWith(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(AlignLeftWithProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignLeftWithProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> AlignLeftWithProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignLeftWith");
+
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignRightWithPanel XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignRightWithPanel XAML attached property value of the specified
+        /// object. (true to align this element's right edge with the panel's right edge;
+        /// otherwise, false.)
+        /// </returns>
+        public static bool GetAlignRightWithPanel(AvaloniaObject obj)
+        {
+            return (bool)obj.GetValue(AlignRightWithPanelProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">
+        /// The value to set. (true to align this element's right edge with the panel's right
+        /// edge; otherwise, false.)
+        /// </param>
+        public static void SetAlignRightWithPanel(AvaloniaObject obj, bool value)
+        {
+            obj.SetValue(AlignRightWithPanelProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignRightWithPanelProperty"/> XAML attached property.
+        /// </summary>
+        public static readonly AttachedProperty<bool> AlignRightWithPanelProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, bool>("AlignRightWithPanel", false);
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignRightWith XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignRightWith XAML attached property value of the specified
+        /// object. (The element to align this element's right edge with.)
+        /// </returns>        
+        [ResolveByName]
+        public static object GetAlignRightWith(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(AlignRightWithProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.AlignRightWith XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to align this element's right edge with.)</param>
+        public static void SetAlignRightWith(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(AlignRightWithProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignRightWithProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> AlignRightWithProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignRightWith");
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignTopWithPanel XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignTopWithPanel XAML attached property value of the specified
+        /// object. (true to align this element's top edge with the panel's top edge; otherwise,
+        /// false.)
+        /// </returns>
+        public static bool GetAlignTopWithPanel(AvaloniaObject obj)
+        {
+            return (bool)obj.GetValue(AlignTopWithPanelProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.AlignTopWithPanel XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">
+        /// The value to set. (true to align this element's top edge with the panel's top
+        /// edge; otherwise, false.)
+        /// </param>
+        public static void SetAlignTopWithPanel(AvaloniaObject obj, bool value)
+        {
+            obj.SetValue(AlignTopWithPanelProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignTopWithPanelProperty"/> XAML attached property.
+        /// </summary>
+        public static readonly AttachedProperty<bool> AlignTopWithPanelProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, bool>("AlignTopWithPanel", false);
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignTopWith XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>The value to set. (The element to align this element's top edge with.)</returns>        
+        [ResolveByName]
+        public static object GetAlignTopWith(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(AlignTopWithProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.AlignTopWith XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to align this element's top edge with.)</param>
+        public static void SetAlignTopWith(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(AlignTopWithProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignTopWithProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> AlignTopWithProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignTopWith");
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignVerticalCenterWithPanel XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.AlignVerticalCenterWithPanel XAML attached property value of
+        /// the specified object. (true to vertically center this element in the panel; otherwise,
+        /// false.)
+        /// </returns>
+        public static bool GetAlignVerticalCenterWithPanel(AvaloniaObject obj)
+        {
+            return (bool)obj.GetValue(AlignVerticalCenterWithPanelProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.AlignVerticalCenterWithPanel XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">
+        /// The value to set. (true to vertically center this element in the panel; otherwise,
+        /// false.)
+        /// </param>
+        public static void SetAlignVerticalCenterWithPanel(AvaloniaObject obj, bool value)
+        {
+            obj.SetValue(AlignVerticalCenterWithPanelProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignVerticalCenterWithPanelProperty"/> XAML attached property.
+        /// </summary>
+        public static readonly AttachedProperty<bool> AlignVerticalCenterWithPanelProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, bool>("AlignVerticalCenterWithPanel", false);
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.AlignVerticalCenterWith XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>The value to set. (The element to align this element's vertical center with.)</returns>        
+        [ResolveByName]
+        public static object GetAlignVerticalCenterWith(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(AlignVerticalCenterWithProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.AlignVerticalCenterWith XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to align this element's horizontal center with.)</param>        
+
+        public static void SetAlignVerticalCenterWith(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(AlignVerticalCenterWithProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.AlignVerticalCenterWithProperty"/> XAML attached property.
+        /// </summary>
+        public static readonly AttachedProperty<object> AlignVerticalCenterWithProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignVerticalCenterWith");
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.Below XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.Below XAML attached property value of the specified object.
+        /// (The element to position this element below.)                                
+        /// </returns>       
+        [ResolveByName]
+        public static object GetBelow(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(BelowProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.Above XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to position this element below.)</param>
+
+        public static void SetBelow(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(BelowProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.BelowProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> BelowProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("Below");
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.LeftOf XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.LeftOf XAML attached property value of the specified object.
+        /// (The element to position this element to the left of.)                                 
+        /// </returns>        
+        [ResolveByName]
+        public static object GetLeftOf(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(LeftOfProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.LeftOf XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to position this element to the left of.)</param>
+        public static void SetLeftOf(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(LeftOfProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.LeftOfProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> LeftOfProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("LeftOf");
+
+        /// <summary>
+        /// Gets the value of the RelativePanel.RightOf XAML attached property for the target element.
+        /// </summary>
+        /// <param name="obj">The object from which the property value is read.</param>
+        /// <returns>
+        /// The RelativePanel.RightOf XAML attached property value of the specified object.
+        /// (The element to position this element to the right of.)                                   
+        /// </returns>        
+        [ResolveByName]
+        public static object GetRightOf(AvaloniaObject obj)
+        {
+            return (object)obj.GetValue(RightOfProperty);
+        }
+
+        /// <summary>
+        /// Sets the value of the RelativePanel.RightOf XAML attached property for a target element.
+        /// </summary>
+        /// <param name="obj">The object to which the property value is written.</param>
+        /// <param name="value">The value to set. (The element to position this element to the right of.)</param>
+        public static void SetRightOf(AvaloniaObject obj, object value)
+        {
+            obj.SetValue(RightOfProperty, value);
+        }
+
+        /// <summary>
+        ///  Identifies the <see cref="RelativePanel.RightOfProperty"/> XAML attached property.
+        /// </summary>
+
+        public static readonly AttachedProperty<object> RightOfProperty =
+            AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("RightOf");
+    }
+}

+ 536 - 0
src/Avalonia.Controls/RelativePanel.cs

@@ -0,0 +1,536 @@
+// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Layout;
+
+#nullable enable
+
+namespace Avalonia.Controls
+{
+    public partial class RelativePanel : Panel
+    {
+        private readonly Graph _childGraph;
+
+        public RelativePanel() => _childGraph = new Graph();
+
+        private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child)
+        {
+            var dependency = child.GetValue(property);
+
+            if (dependency is Layoutable layoutable)
+            {
+                if (Children.Contains((ILayoutable)layoutable))
+                    return layoutable;
+
+                throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}");
+            }
+
+            return null;
+        }
+
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            _childGraph.Clear();
+            foreach (Layoutable child in Children)
+            {
+                if (child == null)
+                    continue;
+                var node = _childGraph.AddNode(child);
+
+                node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child));
+                node.AlignTopWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignTopWithProperty, child));
+                node.AlignRightWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignRightWithProperty, child));
+                node.AlignBottomWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignBottomWithProperty, child));
+
+                node.LeftOfNode = _childGraph.AddLink(node, GetDependencyElement(LeftOfProperty, child));
+                node.AboveNode = _childGraph.AddLink(node, GetDependencyElement(AboveProperty, child));
+                node.RightOfNode = _childGraph.AddLink(node, GetDependencyElement(RightOfProperty, child));
+                node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child));
+
+                node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child));
+                node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child));
+
+            }
+            _childGraph.Measure(availableSize);
+
+            _childGraph.Reset(false);
+            var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN());
+            _childGraph.Reset();
+            _childGraph.Measure(boundingSize);
+            return boundingSize;
+        }
+
+        protected override Size ArrangeOverride(Size arrangeSize)
+        {
+            _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize));
+            return arrangeSize;
+        }
+
+        private class GraphNode
+        {
+            public bool Measured { get; set; }
+
+            public Layoutable Element { get; }
+
+            private bool HorizontalOffsetFlag { get; set; }
+
+            private bool VerticalOffsetFlag { get; set; }
+
+            private Size BoundingSize { get; set; }
+
+            public Size OriginDesiredSize { get; set; }
+
+            public double Left { get; set; } = double.NaN;
+
+            public double Top { get; set; } = double.NaN;
+
+            public double Right { get; set; } = double.NaN;
+
+            public double Bottom { get; set; } = double.NaN;
+
+            public HashSet<GraphNode> OutgoingNodes { get; }
+
+            public GraphNode? AlignLeftWithNode { get; set; }
+
+            public GraphNode? AlignTopWithNode { get; set; }
+
+            public GraphNode? AlignRightWithNode { get; set; }
+
+            public GraphNode? AlignBottomWithNode { get; set; }
+
+            public GraphNode? LeftOfNode { get; set; }
+
+            public GraphNode? AboveNode { get; set; }
+
+            public GraphNode? RightOfNode { get; set; }
+
+            public GraphNode? BelowNode { get; set; }
+
+            public GraphNode? AlignHorizontalCenterWith { get; set; }
+
+            public GraphNode? AlignVerticalCenterWith { get; set; }
+
+            public GraphNode(Layoutable element)
+            {
+                OutgoingNodes = new HashSet<GraphNode>();
+                Element = element;
+            }
+
+            public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0)));
+
+            public void Reset(bool clearPos)
+            {
+                if (clearPos)
+                {
+                    Left = double.NaN;
+                    Top = double.NaN;
+                    Right = double.NaN;
+                    Bottom = double.NaN;
+                }
+
+                Measured = false;
+            }
+
+            public Size GetBoundingSize()
+            {
+                if (Left < 0 || Top < 0) return default;
+                if (Measured)
+                    return BoundingSize;
+
+                if (!OutgoingNodes.Any())
+                {
+                    BoundingSize = Element.DesiredSize;
+                    Measured = true;
+                }
+                else
+                {
+                    BoundingSize = GetBoundingSize(this, Element.DesiredSize, OutgoingNodes);
+                    Measured = true;
+                }
+
+                return BoundingSize;
+            }
+
+            private static Size GetBoundingSize(GraphNode prevNode, Size prevSize, IEnumerable<GraphNode> nodes)
+            {
+                foreach (var node in nodes)
+                {
+                    if (node.Measured || !node.OutgoingNodes.Any())
+                    {
+                        if (prevNode.LeftOfNode != null && prevNode.LeftOfNode == node ||
+                            prevNode.RightOfNode != null && prevNode.RightOfNode == node)
+                        {
+                            prevSize = prevSize.WithWidth(prevSize.Width + node.BoundingSize.Width);
+                            if (GetAlignHorizontalCenterWithPanel(node.Element) || node.HorizontalOffsetFlag)
+                            {
+                                prevSize = prevSize.WithWidth(prevSize.Width + prevNode.OriginDesiredSize.Width);
+                                prevNode.HorizontalOffsetFlag = true;
+                            }
+                            if (node.VerticalOffsetFlag)
+                            {
+                                prevNode.VerticalOffsetFlag = true;
+                            }
+                        }
+
+                        if (prevNode.AboveNode != null && prevNode.AboveNode == node ||
+                            prevNode.BelowNode != null && prevNode.BelowNode == node)
+                        {
+                            prevSize = prevSize.WithHeight(prevSize.Height + node.BoundingSize.Height);
+                            if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag)
+                            {
+                                prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height);
+                                prevNode.VerticalOffsetFlag = true;
+                            }
+                            if (node.HorizontalOffsetFlag)
+                            {
+                                prevNode.HorizontalOffsetFlag = true;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        return GetBoundingSize(node, prevSize, node.OutgoingNodes);
+                    }
+                }
+
+                return prevSize;
+            }
+        }
+
+        private class Graph
+        {
+            private readonly Dictionary<AvaloniaObject, GraphNode> _nodeDic;
+
+            private Size AvailableSize { get; set; }
+
+            public Graph() => _nodeDic = new Dictionary<AvaloniaObject, GraphNode>();
+
+            public IEnumerable<GraphNode> GetNodes() => _nodeDic.Values;
+
+            public void Clear()
+            {
+                AvailableSize = new Size();
+                _nodeDic.Clear();
+            }
+
+            public void Reset(bool clearPos = true) => _nodeDic.Values.Do(node => node.Reset(clearPos));
+
+            public GraphNode? AddLink(GraphNode from, Layoutable? to)
+            {
+                if (to == null)
+                    return null;
+
+                GraphNode nodeTo;
+                if (_nodeDic.ContainsKey(to))
+                {
+                    nodeTo = _nodeDic[to];
+                }
+                else
+                {
+                    nodeTo = new GraphNode(to);
+                    _nodeDic[to] = nodeTo;
+                }
+
+                from.OutgoingNodes.Add(nodeTo);
+                return nodeTo;
+            }
+
+            public GraphNode AddNode(Layoutable value)
+            {
+                if (!_nodeDic.ContainsKey(value))
+                {
+                    var node = new GraphNode(value);
+                    _nodeDic.Add(value, node);
+                    return node;
+                }
+
+                return _nodeDic[value];
+            }
+
+            public void Measure(Size availableSize)
+            {
+                AvailableSize = availableSize;
+                Measure(_nodeDic.Values, null);
+            }
+
+            private void Measure(IEnumerable<GraphNode> nodes, HashSet<AvaloniaObject>? set)
+            {
+                set ??= new HashSet<AvaloniaObject>();
+
+                foreach (var node in nodes)
+                {
+                    if (!node.Measured && !node.OutgoingNodes.Any())
+                    {
+                        MeasureChild(node);
+                        continue;
+                    }
+                    
+                    if (node.OutgoingNodes.All(item => item.Measured))
+                    {
+                        MeasureChild(node);
+                        continue;
+                    }
+                    
+                    if (!set.Add(node.Element))
+                        throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete.");
+                    
+                    Measure(node.OutgoingNodes, set);
+
+                    if (!node.Measured)
+                    {
+                        MeasureChild(node);
+                    }
+                }
+            }
+
+            private void MeasureChild(GraphNode node)
+            {
+                var child = node.Element;
+                child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+                node.OriginDesiredSize = child.DesiredSize;
+
+                var alignLeftWithPanel = GetAlignLeftWithPanel(child);
+                var alignTopWithPanel = GetAlignTopWithPanel(child);
+                var alignRightWithPanel = GetAlignRightWithPanel(child);
+                var alignBottomWithPanel = GetAlignBottomWithPanel(child);
+
+                if (alignLeftWithPanel)
+                    node.Left = 0;
+                if (alignTopWithPanel)
+                    node.Top = 0;
+                if (alignRightWithPanel)
+                    node.Right = 0;
+                if (alignBottomWithPanel)
+                    node.Bottom = 0;
+
+                if (node.AlignLeftWithNode != null)
+                {
+                    node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5;
+                }
+
+                if (node.AlignTopWithNode != null)
+                {
+                    node.Top = node.Top.IsNaN() ? node.AlignTopWithNode.Top : node.AlignTopWithNode.Top * 0.5;
+                }
+
+                if (node.AlignRightWithNode != null)
+                {
+                    node.Right = node.Right.IsNaN()
+                        ? node.AlignRightWithNode.Right
+                        : node.AlignRightWithNode.Right * 0.5;
+                }
+
+                if (node.AlignBottomWithNode != null)
+                {
+                    node.Bottom = node.Bottom.IsNaN()
+                        ? node.AlignBottomWithNode.Bottom
+                        : node.AlignBottomWithNode.Bottom * 0.5;
+                }
+
+                var availableHeight = AvailableSize.Height - node.Top - node.Bottom;
+                if (availableHeight.IsNaN())
+                {
+                    availableHeight = AvailableSize.Height;
+
+                    if (!node.Top.IsNaN() && node.Bottom.IsNaN())
+                    {
+                        availableHeight -= node.Top;
+                    }
+                    else if (node.Top.IsNaN() && !node.Bottom.IsNaN())
+                    {
+                        availableHeight -= node.Bottom;
+                    }
+                }
+
+                var availableWidth = AvailableSize.Width - node.Left - node.Right;
+                if (availableWidth.IsNaN())
+                {
+                    availableWidth = AvailableSize.Width;
+
+                    if (!node.Left.IsNaN() && node.Right.IsNaN())
+                    {
+                        availableWidth -= node.Left;
+                    }
+                    else if (node.Left.IsNaN() && !node.Right.IsNaN())
+                    {
+                        availableWidth -= node.Right;
+                    }
+                }
+
+                child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0)));
+                var childSize = child.DesiredSize;
+
+                if (node.LeftOfNode != null && node.Left.IsNaN())
+                {
+                    node.Left = node.LeftOfNode.Left - childSize.Width;
+                }
+
+                if (node.AboveNode != null && node.Top.IsNaN())
+                {
+                    node.Top = node.AboveNode.Top - childSize.Height;
+                }
+
+                if (node.RightOfNode != null)
+                {
+                    if (node.Right.IsNaN())
+                    {
+                        node.Right = node.RightOfNode.Right - childSize.Width;
+                    }
+
+                    if (node.Left.IsNaN())
+                    {
+                        node.Left = AvailableSize.Width - node.RightOfNode.Right;
+                    }
+                }
+
+                if (node.BelowNode != null)
+                {
+                    if (node.Bottom.IsNaN())
+                    {
+                        node.Bottom = node.BelowNode.Bottom - childSize.Height;
+                    }
+
+                    if (node.Top.IsNaN())
+                    {
+                        node.Top = AvailableSize.Height - node.BelowNode.Bottom;
+                    }
+                }
+
+                if (node.AlignHorizontalCenterWith != null)
+                {
+                    var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5;
+                    var halfWidthRight = (AvailableSize.Width - node.AlignHorizontalCenterWith.Left + node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5;
+
+                    if (node.Left.IsNaN())
+                        node.Left = halfWidthLeft;
+                    else
+                        node.Left = (node.Left + halfWidthLeft) * 0.5;
+
+                    if (node.Right.IsNaN())
+                        node.Right = halfWidthRight;
+                    else
+                        node.Right = (node.Right + halfWidthRight) * 0.5;
+                }
+
+                if (node.AlignVerticalCenterWith != null)
+                {
+                    var halfHeightTop = (AvailableSize.Height + node.AlignVerticalCenterWith.Top - node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5;
+                    var halfHeightBottom = (AvailableSize.Height - node.AlignVerticalCenterWith.Top + node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5;
+
+                    if (node.Top.IsNaN())
+                        node.Top = halfHeightTop;
+                    else
+                        node.Top = (node.Top + halfHeightTop) * 0.5;
+
+                    if (node.Bottom.IsNaN())
+                        node.Bottom = halfHeightBottom;
+                    else
+                        node.Bottom = (node.Bottom + halfHeightBottom) * 0.5;
+                }
+
+                if (GetAlignHorizontalCenterWithPanel(child))
+                {
+                    var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5;
+
+                    if (node.Left.IsNaN())
+                        node.Left = halfSubWidth;
+                    else
+                        node.Left = (node.Left + halfSubWidth) * 0.5;
+
+                    if (node.Right.IsNaN())
+                        node.Right = halfSubWidth;
+                    else
+                        node.Right = (node.Right + halfSubWidth) * 0.5;
+                }
+
+                if (GetAlignVerticalCenterWithPanel(child))
+                {
+                    var halfSubHeight = (AvailableSize.Height - childSize.Height) * 0.5;
+
+                    if (node.Top.IsNaN())
+                        node.Top = halfSubHeight;
+                    else
+                        node.Top = (node.Top + halfSubHeight) * 0.5;
+
+                    if (node.Bottom.IsNaN())
+                        node.Bottom = halfSubHeight;
+                    else
+                        node.Bottom = (node.Bottom + halfSubHeight) * 0.5;
+                }
+
+                if (node.Left.IsNaN())
+                {
+                    if (!node.Right.IsNaN())
+                        node.Left = AvailableSize.Width - node.Right - childSize.Width;
+                    else
+                    {
+                        node.Left = 0;
+                        node.Right = AvailableSize.Width - childSize.Width;
+                    }
+                }
+                else if (!node.Left.IsNaN() && node.Right.IsNaN())
+                {
+                    node.Right = AvailableSize.Width - node.Left - childSize.Width;
+                }
+
+                if (node.Top.IsNaN())
+                {
+                    if (!node.Bottom.IsNaN())
+                        node.Top = AvailableSize.Height - node.Bottom - childSize.Height;
+                    else
+                    {
+                        node.Top = 0;
+                        node.Bottom = AvailableSize.Height - childSize.Height;
+                    }
+                }
+                else if (!node.Top.IsNaN() && node.Bottom.IsNaN())
+                {
+                    node.Bottom = AvailableSize.Height - node.Top - childSize.Height;
+                }
+
+                node.Measured = true;
+            }
+
+            public Size GetBoundingSize(bool calcWidth, bool calcHeight)
+            {
+                var boundingSize = new Size();
+
+                foreach (var node in _nodeDic.Values)
+                {
+                    var size = node.GetBoundingSize();
+                    boundingSize = boundingSize.WithWidth(Math.Max(boundingSize.Width, size.Width));
+                    boundingSize = boundingSize.WithHeight(Math.Max(boundingSize.Height, size.Height));
+                }
+
+                boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : AvailableSize.Width);
+                boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : AvailableSize.Height);
+                return boundingSize;
+            }
+        }
+    }
+
+    internal static partial class Extensions
+    {
+        /// <summary>
+        ///     Returns a value that indicates whether the specified value is not a number ().
+        /// </summary>
+        /// <param name="d">A double-precision floating-point number.</param>
+        /// <returns>true if  evaluates to ; otherwise, false.</returns>
+        public static bool IsNaN(this double d)
+        {
+            return double.IsNaN(d);
+        }
+
+        public static IEnumerable<TSource> Do<TSource>(this IEnumerable<TSource> source, Action<TSource> predicate)
+        {
+            var enumerable = source as IList<TSource> ?? source.ToList();
+            foreach (var item in enumerable)
+            {
+                predicate.Invoke(item);
+            }
+
+            return enumerable;
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server
                             GetAvaloniaInputModifiers(pressed.Modifiers)));
                     }, DispatcherPriority.Input);
                 }
-                if (obj is PointerPressedEventMessage released)
+                if (obj is PointerReleasedEventMessage released)
                 {
                     Dispatcher.UIThread.Post(() =>
                     {

+ 27 - 0
src/Avalonia.Controls/Repeater/ElementFactory.cs

@@ -0,0 +1,27 @@
+using Avalonia.Controls.Templates;
+
+namespace Avalonia.Controls
+{
+    public abstract class ElementFactory : IElementFactory
+    {
+        public IControl Build(object data)
+        {
+            return GetElementCore(new ElementFactoryGetArgs { Data = data });
+        }
+
+        public IControl GetElement(ElementFactoryGetArgs args)
+        {
+            return GetElementCore(args);
+        }
+
+        public bool Match(object data) => true;
+
+        public void RecycleElement(ElementFactoryRecycleArgs args)
+        {
+            RecycleElementCore(args);
+        }
+
+        protected abstract IControl GetElementCore(ElementFactoryGetArgs args);
+        protected abstract void RecycleElementCore(ElementFactoryRecycleArgs args);
+    }
+}

+ 66 - 0
src/Avalonia.Controls/Repeater/IElementFactory.cs

@@ -0,0 +1,66 @@
+using Avalonia.Controls.Templates;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Represents the optional arguments to use when calling an implementation of the
+    /// <see cref="IElementFactory"/>'s <see cref="IElementFactory.GetElement"/> method.
+    /// </summary>
+    public class ElementFactoryGetArgs
+    {
+        /// <summary>
+        /// Gets or sets the data item for which an appropriate element tree should be realized
+        /// when calling <see cref="IElementFactory.GetElement"/>.
+        /// </summary>
+        public object Data { get; set; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="IControl"/> that is expected to be the parent of the
+        /// realized element from <see cref="IElementFactory.GetElement"/>.
+        /// </summary>
+        public IControl Parent { get; set; }
+
+        /// <summary>
+        /// Gets or sets the index of the item that should be realized.
+        /// </summary>
+        public int Index { get; set; }
+    }
+
+    /// <summary>
+    /// Represents the optional arguments to use when calling an implementation of the
+    /// <see cref="IElementFactory"/>'s <see cref="IElementFactory.GetElement"/> method.
+    /// </summary>
+    public class ElementFactoryRecycleArgs
+    {
+        /// <summary>
+        /// Gets or sets the <see cref="IControl"/> to recycle when calling 
+        /// <see cref="IElementFactory.RecycleElement"/>.
+        /// </summary>
+        public IControl Element { get; set; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="IControl"/> that is expected to be the parent of the
+        /// realized element from <see cref="IElementFactory.GetElement"/>.
+        /// </summary>
+        public IControl Parent { get; set; }
+    }
+
+    /// <summary>
+    /// A data template that supports creating and recyling elements for an <see cref="ItemsRepeater"/>.
+    /// </summary>
+    public interface IElementFactory : IDataTemplate
+    {
+        /// <summary>
+        /// Gets an <see cref="IControl"/>.
+        /// </summary>
+        /// <param name="args">The element args.</param>
+        public IControl GetElement(ElementFactoryGetArgs args);
+
+        /// <summary>
+        /// Recycles an <see cref="IControl"/> that was previously retrieved using
+        /// <see cref="GetElement"/>.
+        /// </summary>
+        /// <param name="args">The recycle args.</param>
+        public void RecycleElement(ElementFactoryRecycleArgs args);
+    }
+}

+ 16 - 3
src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs

@@ -7,13 +7,26 @@ using Avalonia.Controls.Templates;
 
 namespace Avalonia.Controls
 {
-    internal class ItemTemplateWrapper
+    internal class ItemTemplateWrapper : IElementFactory
     {
         private readonly IDataTemplate _dataTemplate;
 
         public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate;
 
-        public IControl GetElement(IControl parent, object data)
+        public IControl Build(object param) => GetElement(null, param);
+        public bool Match(object data) => _dataTemplate.Match(data);
+
+        public IControl GetElement(ElementFactoryGetArgs args)
+        {
+            return GetElement(args.Parent, args.Data);
+        }
+
+        public void RecycleElement(ElementFactoryRecycleArgs args)
+        {
+            RecycleElement(args.Parent, args.Element);
+        }
+
+        private IControl GetElement(IControl parent, object data)
         {
             var selectedTemplate = _dataTemplate;
             var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate);
@@ -37,7 +50,7 @@ namespace Avalonia.Controls
             return element;
         }
 
-        public void RecycleElement(IControl parent, IControl element)
+        private void RecycleElement(IControl parent, IControl element)
         {
             var selectedTemplate = _dataTemplate;
             var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate);

+ 2 - 2
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -141,7 +141,7 @@ namespace Avalonia.Controls
         /// </summary>
         public ItemsSourceView ItemsSourceView { get; private set; }
 
-        internal ItemTemplateWrapper ItemTemplateShim { get; set; }
+        internal IElementFactory ItemTemplateShim { get; set; }
         internal Point LayoutOrigin { get; set; }
         internal object LayoutState { get; set; }
         internal IControl MadeAnchor => _viewportManager.MadeAnchor;
@@ -664,7 +664,7 @@ namespace Avalonia.Controls
                 }
             }
 
-            ItemTemplateShim = new ItemTemplateWrapper(newValue);
+            ItemTemplateShim = newValue as IElementFactory ?? new ItemTemplateWrapper(newValue);
 
             InvalidateMeasure();
         }

+ 9 - 3
src/Avalonia.Controls/Repeater/RecyclePool.cs

@@ -11,10 +11,13 @@ using Avalonia.Controls.Templates;
 
 namespace Avalonia.Controls
 {
-    internal class RecyclePool
+    public class RecyclePool
     {
-        public static readonly AttachedProperty<IDataTemplate> OriginTemplateProperty =
-            AvaloniaProperty.RegisterAttached<Control, IDataTemplate>("OriginTemplate", typeof(RecyclePool));
+        internal static readonly AttachedProperty<IDataTemplate> OriginTemplateProperty =
+            AvaloniaProperty.RegisterAttached<RecyclePool, Control, IDataTemplate>("OriginTemplate");
+
+        internal static readonly AttachedProperty<string> ReuseKeyProperty =
+            AvaloniaProperty.RegisterAttached<RecyclePool, Control, string>("ReuseKey", string.Empty);
 
         private static ConditionalWeakTable<IDataTemplate, RecyclePool> s_pools = new ConditionalWeakTable<IDataTemplate, RecyclePool>();
         private readonly Dictionary<string, List<ElementInfo>> _elements = new Dictionary<string, List<ElementInfo>>();
@@ -77,6 +80,9 @@ namespace Avalonia.Controls
             return null;
         }
 
+        internal string GetReuseKey(IControl element) => element.GetValue(ReuseKeyProperty);
+        internal void SetReuseKey(IControl element, string value) => element.SetValue(ReuseKeyProperty, value);
+
         private IPanel EnsureOwnerIsPanelOrNull(IControl owner)
         {
             if (owner is IPanel panel)

部分文件因为文件数量过多而无法显示