浏览代码

Merge branch 'master' into move-name-generator

Max Katz 2 年之前
父节点
当前提交
fa6fb50620
共有 100 个文件被更改,包括 2105 次插入1076 次删除
  1. 16 2
      azure-pipelines-integrationtests.yml
  2. 2 5
      native/Avalonia.Native/src/OSX/AvnView.mm
  3. 1 1
      native/Avalonia.Native/src/OSX/Screens.mm
  4. 17 7
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  5. 5 0
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  6. 2 2
      nukebuild/Build.cs
  7. 5 0
      packages/Avalonia/Avalonia.props
  8. 1 0
      samples/BindingDemo/BindingDemo.csproj
  9. 1 1
      samples/ControlCatalog.Android/Resources/values/styles.xml
  10. 8 1
      samples/ControlCatalog.Browser/app.css
  11. 2 0
      samples/ControlCatalog.NetCore/Program.cs
  12. 2 2
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  13. 1 5
      samples/ControlCatalog.iOS/Info.plist
  14. 2 2
      samples/ControlCatalog/App.xaml.cs
  15. 37 1
      samples/ControlCatalog/MainView.xaml.cs
  16. 0 1
      samples/ControlCatalog/MainWindow.xaml.cs
  17. 1 1
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  18. 16 6
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  19. 26 4
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  20. 1 0
      samples/GpuInterop/GpuInterop.csproj
  21. 41 34
      samples/IntegrationTestApp/ShowWindowTest.axaml
  22. 25 6
      samples/IntegrationTestApp/ShowWindowTest.axaml.cs
  23. 9 2
      samples/IntegrationTestApp/bundle.sh
  24. 1 0
      samples/MobileSandbox/MobileSandbox.csproj
  25. 1 0
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  26. 1 0
      samples/Previewer/Previewer.csproj
  27. 1 0
      samples/ReactiveUIDemo/ReactiveUIDemo.csproj
  28. 1 0
      samples/RenderDemo/RenderDemo.csproj
  29. 1 0
      samples/Sandbox/Sandbox.csproj
  30. 1 0
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  31. 15 0
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  32. 6 0
      src/Android/Avalonia.Android/AvaloniaView.cs
  33. 235 0
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  34. 24 19
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  35. 6 1
      src/Avalonia.Base/AttachedProperty.cs
  36. 7 8
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  37. 4 8
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  38. 18 34
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  39. 99 31
      src/Avalonia.Base/Media/FontManager.cs
  40. 290 0
      src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs
  41. 4 0
      src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs
  42. 32 34
      src/Avalonia.Base/Media/Fonts/FontFamilyLoader.cs
  43. 33 0
      src/Avalonia.Base/Media/Fonts/IFontCollection.cs
  44. 107 0
      src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs
  45. 7 7
      src/Avalonia.Base/Media/GlyphRun.cs
  46. 1 1
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  47. 20 0
      src/Avalonia.Base/Media/IGlyphTypeface.cs
  48. 0 2
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  49. 1 1
      src/Avalonia.Base/Media/TextDecoration.cs
  50. 2 2
      src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs
  51. 8 7
      src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
  52. 1 1
      src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
  53. 1 1
      src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
  54. 3 3
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  55. 12 1
      src/Avalonia.Base/Media/Typeface.cs
  56. 104 51
      src/Avalonia.Base/Platform/AssetLoader.cs
  57. 23 7
      src/Avalonia.Base/Platform/IFontManagerImpl.cs
  58. 1 2
      src/Avalonia.Base/Platform/IGlyphRunImpl.cs
  59. 1 1
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  60. 4 4
      src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
  61. 2 3
      src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
  62. 1 2
      src/Avalonia.Base/StyledElement.cs
  63. 6 1
      src/Avalonia.Base/StyledProperty.cs
  64. 4 1
      src/Avalonia.Base/Utilities/UriExtensions.cs
  65. 15 0
      src/Avalonia.Controls/AppBuilder.cs
  66. 34 0
      src/Avalonia.Controls/Automation/Peers/LabelAutomationPeer.cs
  67. 21 24
      src/Avalonia.Controls/Button.cs
  68. 37 53
      src/Avalonia.Controls/Calendar/Calendar.cs
  69. 5 9
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  70. 27 41
      src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs
  71. 23 29
      src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
  72. 12 16
      src/Avalonia.Controls/ComboBox.cs
  73. 94 89
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  74. 89 92
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  75. 52 41
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  76. 29 39
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  77. 4 4
      src/Avalonia.Controls/Documents/InlineCollection.cs
  78. 9 14
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  79. 1 8
      src/Avalonia.Controls/ItemsControl.cs
  80. 11 16
      src/Avalonia.Controls/Label.cs
  81. 9 13
      src/Avalonia.Controls/MenuItem.cs
  82. 2 2
      src/Avalonia.Controls/NativeMenu.cs
  83. 60 82
      src/Avalonia.Controls/NativeMenuItem.cs
  84. 2 2
      src/Avalonia.Controls/NativeMenuItemBase.cs
  85. 4 5
      src/Avalonia.Controls/Notifications/NotificationCard.cs
  86. 38 52
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  87. 24 11
      src/Avalonia.Controls/Panel.cs
  88. 55 0
      src/Avalonia.Controls/Platform/IInsetsManager.cs
  89. 4 7
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  90. 2 1
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  91. 47 0
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  92. 11 20
      src/Avalonia.Controls/Primitives/Popup.cs
  93. 2 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  94. 13 18
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  95. 30 29
      src/Avalonia.Controls/RadioButton.cs
  96. 4 8
      src/Avalonia.Controls/SplitButton/SplitButton.cs
  97. 4 1
      src/Avalonia.Controls/TopLevel.cs
  98. 4 10
      src/Avalonia.Controls/TrayIcon.cs
  99. 2 2
      src/Avalonia.Controls/TreeViewItem.cs
  100. 15 20
      src/Avalonia.Controls/Window.cs

+ 16 - 2
azure-pipelines-integrationtests.yml

@@ -15,28 +15,40 @@ jobs:
       version: 7.0.101
       version: 7.0.101
       
       
   - script: system_profiler SPDisplaysDataType |grep Resolution
   - script: system_profiler SPDisplaysDataType |grep Resolution
+    displayName: 'Get Resolution'
   
   
   - script: |
   - script: |
+      arch="x64"
+      if [[ $(uname -m) == 'arm64' ]]; then
+      arch="arm64"
+      fi
       sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
       sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
       pkill node
       pkill node
-      appium &
+      appium > appium.out &
       pkill IntegrationTestApp
       pkill IntegrationTestApp
       ./build.sh CompileNative
       ./build.sh CompileNative
       rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")")
       rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")")
       pkill IntegrationTestApp
       pkill IntegrationTestApp
       ./samples/IntegrationTestApp/bundle.sh
       ./samples/IntegrationTestApp/bundle.sh
-      open -n ./samples/IntegrationTestApp/bin/Debug/net7.0/osx-arm64/publish/IntegrationTestApp.app
+      open -n ./samples/IntegrationTestApp/bin/Debug/net7.0/osx-$arch/publish/IntegrationTestApp.app
       pkill IntegrationTestApp
       pkill IntegrationTestApp
+    displayName: 'Build IntegrationTestApp'
 
 
   - task: DotNetCoreCLI@2
   - task: DotNetCoreCLI@2
+    displayName: 'Run Integration Tests'
     inputs:
     inputs:
       command: 'test'
       command: 'test'
       projects: 'tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj'
       projects: 'tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj'
+      arguments: '-l "console;verbosity=detailed"'
 
 
   - script: |
   - script: |
       pkill IntegrationTestApp
       pkill IntegrationTestApp
       pkill node
       pkill node
+    displayName: 'Stop Appium'
 
 
+  - publish: appium.out
+    displayName: 'Publish appium logs on failure'
+    condition: failed()
 
 
 - job: Windows
 - job: Windows
   pool:
   pool:
@@ -60,11 +72,13 @@ jobs:
     displayName: 'Start WinAppDriver'
     displayName: 'Start WinAppDriver'
   
   
   - task: DotNetCoreCLI@2
   - task: DotNetCoreCLI@2
+    displayName: 'Build IntegrationTestApp'
     inputs:
     inputs:
       command: 'build'
       command: 'build'
       projects: 'samples/IntegrationTestApp/IntegrationTestApp.csproj'
       projects: 'samples/IntegrationTestApp/IntegrationTestApp.csproj'
 
 
   - task: DotNetCoreCLI@2
   - task: DotNetCoreCLI@2
+    displayName: 'Run Integration Tests'
     retryCountOnTaskFailure: 3
     retryCountOnTaskFailure: 3
     inputs:
     inputs:
       command: 'test'
       command: 'test'

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

@@ -127,11 +127,8 @@
         [self updateRenderTarget];
         [self updateRenderTarget];
 
 
         auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
         auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
-        
-        if(_parent->IsShown())
-        {
-            _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
-        }
+
+        _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
     }
     }
 }
 }
 
 

+ 1 - 1
native/Avalonia.Native/src/OSX/Screens.mm

@@ -41,7 +41,7 @@ public:
             ret->WorkingArea.X = [screen visibleFrame].origin.x;
             ret->WorkingArea.X = [screen visibleFrame].origin.x;
             ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
             ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
             
             
-            ret->Scaling = [screen backingScaleFactor];
+            ret->Scaling = 1;
             
             
             ret->IsPrimary = index == 0;
             ret->IsPrimary = index == 0;
             
             

+ 17 - 7
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@@ -4,6 +4,7 @@
 //
 //
 
 
 #import <AppKit/AppKit.h>
 #import <AppKit/AppKit.h>
+#import <Cocoa/Cocoa.h>
 #include "common.h"
 #include "common.h"
 #include "AvnView.h"
 #include "AvnView.h"
 #include "menu.h"
 #include "menu.h"
@@ -293,15 +294,24 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
         }
         }
 
 
         @try {
         @try {
-            if(x != lastSize.width || y != lastSize.height) {
-                lastSize = NSSize{x, y};
-
+            if(x != lastSize.width || y != lastSize.height)
+            {
                 if (!_shown) {
                 if (!_shown) {
-                    BaseEvents->Resized(AvnSize{x, y}, reason);
-                } else if (Window != nullptr) {
-                    [Window setContentSize:lastSize];
-                    [Window invalidateShadow];
+                    auto screenSize = [Window screen].visibleFrame.size;
+
+                    if (x > screenSize.width) {
+                        x = screenSize.width;
+                    }
+
+                    if (y > screenSize.height) {
+                        y = screenSize.height;
+                    }
                 }
                 }
+
+                lastSize = NSSize{x, y};
+
+                [Window setContentSize:lastSize];
+                [Window invalidateShadow];
             }
             }
         }
         }
         @finally {
         @finally {

+ 5 - 0
native/Avalonia.Native/src/OSX/WindowImpl.mm

@@ -54,6 +54,11 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
 
 
         WindowBaseImpl::Show(activate, isDialog);
         WindowBaseImpl::Show(activate, isDialog);
         GetWindowState(&_actualWindowState);
         GetWindowState(&_actualWindowState);
+
+        if(IsZoomed()) {
+            _lastWindowState = _actualWindowState;
+        }
+
         return SetWindowState(_lastWindowState);
         return SetWindowState(_lastWindowState);
     }
     }
 }
 }

+ 2 - 2
nukebuild/Build.cs

@@ -165,10 +165,10 @@ partial class Build : NukeBuild
         foreach (var fw in targetFrameworks)
         foreach (var fw in targetFrameworks)
         {
         {
             if (fw.StartsWith("net4")
             if (fw.StartsWith("net4")
-                && RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
+                && (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                 && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1")
                 && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1")
             {
             {
-                Information($"Skipping {projectName} ({fw}) tests on Linux - https://github.com/mono/mono/issues/13969");
+                Information($"Skipping {projectName} ({fw}) tests on *nix - https://github.com/mono/mono/issues/13969");
                 continue;
                 continue;
             }
             }
 
 

+ 5 - 0
packages/Avalonia/Avalonia.props

@@ -6,4 +6,9 @@
     <AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
     <AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
   <Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
+
+  <!-- Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package -->
+  <ItemGroup>
+    <ProjectCapability Include="Avalonia"/>
+  </ItemGroup>
 </Project>
 </Project>

+ 1 - 0
samples/BindingDemo/BindingDemo.csproj

@@ -5,6 +5,7 @@
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />

+ 1 - 1
samples/ControlCatalog.Android/Resources/values/styles.xml

@@ -4,7 +4,7 @@
   <style name="MyTheme">
   <style name="MyTheme">
   </style>
   </style>
 
 
-  <style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
+  <style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.DayNight.NoActionBar">
     <item name="android:windowActionBar">false</item>
     <item name="android:windowActionBar">false</item>
     <item name="android:windowNoTitle">true</item>
     <item name="android:windowNoTitle">true</item>
   </style>
   </style>

+ 8 - 1
samples/ControlCatalog.Browser/app.css

@@ -1,4 +1,11 @@
-#out {
+:root {
+    --sat: env(safe-area-inset-top);
+    --sar: env(safe-area-inset-right);
+    --sab: env(safe-area-inset-bottom);
+    --sal: env(safe-area-inset-left);
+}
+
+#out {
     height: 100vh;
     height: 100vh;
     width: 100vw
     width: 100vw
 }
 }

+ 2 - 0
samples/ControlCatalog.NetCore/Program.cs

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
 using Avalonia;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Fonts.Inter;
 using Avalonia.Headless;
 using Avalonia.Headless;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
 using Avalonia.Threading;
 using Avalonia.Threading;
@@ -124,6 +125,7 @@ namespace ControlCatalog.NetCore
                     EnableIme = true
                     EnableIme = true
                 })
                 })
                 .UseSkia()
                 .UseSkia()
+                .WithInterFont()
                 .AfterSetup(builder =>
                 .AfterSetup(builder =>
                 {
                 {
                     builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
                     builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()

+ 2 - 2
samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj

@@ -3,7 +3,7 @@
     <OutputType>Exe</OutputType>
     <OutputType>Exe</OutputType>
     <ProvisioningType>manual</ProvisioningType>
     <ProvisioningType>manual</ProvisioningType>
     <TargetFramework>net6.0-ios</TargetFramework>
     <TargetFramework>net6.0-ios</TargetFramework>
-    <SupportedOSPlatformVersion>10.0</SupportedOSPlatformVersion>
+    <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
     <!-- temporal workaround for our GL interface backend -->
     <!-- temporal workaround for our GL interface backend -->
     <UseInterpreter>True</UseInterpreter>
     <UseInterpreter>True</UseInterpreter>
     <RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
     <RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
@@ -16,4 +16,4 @@
     <ProjectReference Include="..\..\src\iOS\Avalonia.iOS\Avalonia.iOS.csproj" />
     <ProjectReference Include="..\..\src\iOS\Avalonia.iOS\Avalonia.iOS.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
   </ItemGroup>
-</Project>
+</Project>

+ 1 - 5
samples/ControlCatalog.iOS/Info.plist

@@ -13,7 +13,7 @@
 	<key>LSRequiresIPhoneOS</key>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
 	<true/>
 	<key>MinimumOSVersion</key>
 	<key>MinimumOSVersion</key>
-	<string>10.0</string>
+	<string>13.0</string>
 	<key>UIDeviceFamily</key>
 	<key>UIDeviceFamily</key>
 	<array>
 	<array>
 		<integer>1</integer>
 		<integer>1</integer>
@@ -39,9 +39,5 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
 	</array>
-	<key>UIStatusBarHidden</key>
-	<true/>
-	<key>UIViewControllerBasedStatusBarAppearance</key>
-	<false/>
 </dict>
 </dict>
 </plist>
 </plist>

+ 2 - 2
samples/ControlCatalog/App.xaml.cs

@@ -44,11 +44,11 @@ namespace ControlCatalog
         {
         {
             if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
             if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
             {
             {
-                desktopLifetime.MainWindow = new MainWindow();
+                desktopLifetime.MainWindow = new MainWindow { DataContext = new MainWindowViewModel() };
             }
             }
             else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
             else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
             {
             {
-                singleViewLifetime.MainView = new MainView();
+                singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() };
             }
             }
 
 
             base.OnFrameworkInitializationCompleted();
             base.OnFrameworkInitializationCompleted();

+ 37 - 1
samples/ControlCatalog/MainView.xaml.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections;
 using System.Collections;
+using System.Threading.Tasks;
 using Avalonia;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
@@ -12,6 +13,7 @@ using Avalonia.VisualTree;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using ControlCatalog.Models;
 using ControlCatalog.Models;
 using ControlCatalog.Pages;
 using ControlCatalog.Pages;
+using ControlCatalog.ViewModels;
 
 
 namespace ControlCatalog
 namespace ControlCatalog
 {
 {
@@ -99,13 +101,47 @@ namespace ControlCatalog
             };
             };
         }
         }
 
 
+        internal MainWindowViewModel ViewModel => (MainWindowViewModel)DataContext!;
+        
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
         {
             base.OnAttachedToVisualTree(e);
             base.OnAttachedToVisualTree(e);
+
             var decorations = this.Get<ComboBox>("Decorations");
             var decorations = this.Get<ComboBox>("Decorations");
             if (VisualRoot is Window window)
             if (VisualRoot is Window window)
                 decorations.SelectedIndex = (int)window.SystemDecorations;
                 decorations.SelectedIndex = (int)window.SystemDecorations;
-            
+
+            var insets = TopLevel.GetTopLevel(this)!.InsetsManager;
+            if (insets != null)
+            {
+                // In real life application these events should be unsubscribed to avoid memory leaks.
+                ViewModel.SafeAreaPadding = insets.SafeAreaPadding;
+                insets.SafeAreaChanged += (sender, args) =>
+                {
+                    ViewModel.SafeAreaPadding = insets.SafeAreaPadding;
+                };
+
+                ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge;
+                ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
+
+                ViewModel.PropertyChanged += async (sender, args) =>
+                {
+                    if (args.PropertyName == nameof(ViewModel.DisplayEdgeToEdge))
+                    {
+                        insets.DisplayEdgeToEdge = ViewModel.DisplayEdgeToEdge;
+                    }
+                    else if (args.PropertyName == nameof(ViewModel.IsSystemBarVisible))
+                    {
+                        insets.IsSystemBarVisible = ViewModel.IsSystemBarVisible;
+                    }
+
+                    // Give the OS some time to apply new values and refresh the view model.
+                    await Task.Delay(100);
+                    ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge;
+                    ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
+                };
+            }
+
             _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
             _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
             PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
             PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
         }
         }

+ 0 - 1
samples/ControlCatalog/MainWindow.xaml.cs

@@ -17,7 +17,6 @@ namespace ControlCatalog
         {
         {
             this.InitializeComponent();
             this.InitializeComponent();
 
 
-            DataContext = new MainWindowViewModel();
             _recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
             _recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
         }
         }
 
 

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

@@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
         {
         {
             AvaloniaXamlLoader.Load(this);
             AvaloniaXamlLoader.Load(this);
             var fontComboBox = this.Get<ComboBox>("fontComboBox");
             var fontComboBox = this.Get<ComboBox>("fontComboBox");
-            fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x));
+            fontComboBox.Items = FontManager.Current.SystemFonts;
             fontComboBox.SelectedIndex = 0;
             fontComboBox.SelectedIndex = 0;
         }
         }
     }
     }

+ 16 - 6
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@@ -5,11 +5,21 @@
              xmlns:viewModels="using:ControlCatalog.ViewModels"
              xmlns:viewModels="using:ControlCatalog.ViewModels"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="ControlCatalog.Pages.WindowCustomizationsPage"
              x:Class="ControlCatalog.Pages.WindowCustomizationsPage"
-             x:DataType="viewModels:MainWindowViewModel">
-  <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}" />
+             x:DataType="viewModels:MainWindowViewModel"
+             x:CompileBindings="True">
+  <StackPanel>
+    <StackPanel Spacing="10" Margin="25" IsEnabled="{OnFormFactor false, Desktop=true}">
+      <TextBlock Classes="h2" Text="Desktop properties" Margin="4" />
+      <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}" />
+    </StackPanel>
+    <StackPanel Spacing="10" Margin="25" IsEnabled="{OnFormFactor false, Mobile=true}">
+      <TextBlock Classes="h2" Text="Mobile properties" Margin="4" />
+      <CheckBox Content="Is System Bar Visible" IsChecked="{Binding IsSystemBarVisible}" />
+      <CheckBox Content="Display Edge To Edge" IsChecked="{Binding DisplayEdgeToEdge}" />
+      <TextBlock Text="{Binding SafeAreaPadding, StringFormat='Safe Area Padding: {0}'}" />
+    </StackPanel>
   </StackPanel>
   </StackPanel>
 </UserControl>
 </UserControl>

+ 26 - 4
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@@ -6,6 +6,7 @@ using Avalonia.Platform;
 using Avalonia.Reactive;
 using Avalonia.Reactive;
 using System;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
+using Avalonia;
 using MiniMvvm;
 using MiniMvvm;
 
 
 namespace ControlCatalog.ViewModels
 namespace ControlCatalog.ViewModels
@@ -20,6 +21,9 @@ namespace ControlCatalog.ViewModels
         private bool _systemTitleBarEnabled;
         private bool _systemTitleBarEnabled;
         private bool _preferSystemChromeEnabled;
         private bool _preferSystemChromeEnabled;
         private double _titleBarHeight;
         private double _titleBarHeight;
+        private bool _isSystemBarVisible;
+        private bool _displayEdgeToEdge;
+        private Thickness _safeAreaPadding;
 
 
         public MainWindowViewModel()
         public MainWindowViewModel()
         {
         {
@@ -78,25 +82,25 @@ namespace ControlCatalog.ViewModels
         {
         {
             get { return _chromeHints; }
             get { return _chromeHints; }
             set { this.RaiseAndSetIfChanged(ref _chromeHints, value); }
             set { this.RaiseAndSetIfChanged(ref _chromeHints, value); }
-        }        
+        }
 
 
         public bool ExtendClientAreaEnabled
         public bool ExtendClientAreaEnabled
         {
         {
             get { return _extendClientAreaEnabled; }
             get { return _extendClientAreaEnabled; }
             set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); }
             set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); }
-        }        
+        }
 
 
         public bool SystemTitleBarEnabled
         public bool SystemTitleBarEnabled
         {
         {
             get { return _systemTitleBarEnabled; }
             get { return _systemTitleBarEnabled; }
             set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); }
             set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); }
-        }        
+        }
 
 
         public bool PreferSystemChromeEnabled
         public bool PreferSystemChromeEnabled
         {
         {
             get { return _preferSystemChromeEnabled; }
             get { return _preferSystemChromeEnabled; }
             set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); }
             set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); }
-        }        
+        }
 
 
         public double TitleBarHeight
         public double TitleBarHeight
         {
         {
@@ -122,6 +126,24 @@ namespace ControlCatalog.ViewModels
             set { this.RaiseAndSetIfChanged(ref _isMenuItemChecked, value); }
             set { this.RaiseAndSetIfChanged(ref _isMenuItemChecked, value); }
         }
         }
 
 
+        public bool IsSystemBarVisible
+        {
+            get { return _isSystemBarVisible; }
+            set { this.RaiseAndSetIfChanged(ref _isSystemBarVisible, value); }
+        }
+
+        public bool DisplayEdgeToEdge
+        {
+            get { return _displayEdgeToEdge; }
+            set { this.RaiseAndSetIfChanged(ref _displayEdgeToEdge, value); }
+        }
+        
+        public Thickness SafeAreaPadding
+        {
+            get { return _safeAreaPadding; }
+            set { this.RaiseAndSetIfChanged(ref _safeAreaPadding, value); }
+        }
+
         public MiniCommand AboutCommand { get; }
         public MiniCommand AboutCommand { get; }
 
 
         public MiniCommand ExitCommand { get; }
         public MiniCommand ExitCommand { get; }

+ 1 - 0
samples/GpuInterop/GpuInterop.csproj

@@ -15,6 +15,7 @@
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 

+ 41 - 34
samples/IntegrationTestApp/ShowWindowTest.axaml

@@ -1,41 +1,48 @@
 <Window xmlns="https://github.com/avaloniaui"
 <Window xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:integrationTestApp="clr-namespace:IntegrationTestApp"
         x:Class="IntegrationTestApp.ShowWindowTest"
         x:Class="IntegrationTestApp.ShowWindowTest"
         Name="SecondaryWindow"
         Name="SecondaryWindow"
         x:DataType="Window"
         x:DataType="Window"
         Title="Show Window Test">
         Title="Show Window Test">
-  <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
-    <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
-    <TextBox Name="ClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
-             Text="{Binding ClientSize, Mode=OneWay}"/>
-    
-    <Label Grid.Column="0" Grid.Row="2">Frame Size</Label>
-    <TextBox Name="FrameSize" Grid.Column="1" Grid.Row="2" IsReadOnly="True"
-             Text="{Binding FrameSize, Mode=OneWay}"/>
-
-    <Label Grid.Column="0" Grid.Row="3">Position</Label>
-    <TextBox Name="Position" Grid.Column="1" Grid.Row="3" IsReadOnly="True"/>
-
-    <Label Grid.Column="0" Grid.Row="4">Owner Rect</Label>
-    <TextBox Name="OwnerRect" Grid.Column="1" Grid.Row="4" IsReadOnly="True"/>
-    
-    <Label Grid.Column="0" Grid.Row="5">Screen Rect</Label>
-    <TextBox Name="ScreenRect" Grid.Column="1" Grid.Row="5" IsReadOnly="True"/>
-
-    <Label Grid.Column="0" Grid.Row="6">Scaling</Label>
-    <TextBox Name="Scaling" Grid.Column="1" Grid.Row="6" IsReadOnly="True"/>
-    
-    <Label Grid.Column="0" Grid.Row="7">WindowState</Label>
-    <ComboBox Name="WindowState" Grid.Column="1" Grid.Row="7" SelectedIndex="{Binding WindowState}">
-      <ComboBoxItem Name="WindowStateNormal">Normal</ComboBoxItem>
-      <ComboBoxItem Name="WindowStateMinimized">Minimized</ComboBoxItem>
-      <ComboBoxItem Name="WindowStateMaximized">Maximized</ComboBoxItem>
-      <ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem>
-    </ComboBox>
-
-    <Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>
-    <TextBox Name="Order" Grid.Column="1" Grid.Row="8" IsReadOnly="True"/>
-
-    <Button Name="HideButton" Grid.Row="9" Command="{Binding $parent[Window].Hide}">Hide</Button>
-  </Grid>
+  <integrationTestApp:MeasureBorder Name="MyBorder">
+    <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
+      <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
+      <TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
+               Text="{Binding ClientSize, Mode=OneWay}" />
+
+      <Label Grid.Column="0" Grid.Row="2">Frame Size</Label>
+      <TextBox Name="CurrentFrameSize" Grid.Column="1" Grid.Row="2" IsReadOnly="True"
+               Text="{Binding FrameSize, Mode=OneWay}" />
+
+      <Label Grid.Column="0" Grid.Row="3">Position</Label>
+      <TextBox Name="CurrentPosition" Grid.Column="1" Grid.Row="3" IsReadOnly="True" />
+
+      <Label Grid.Column="0" Grid.Row="4">Owner Rect</Label>
+      <TextBox Name="CurrentOwnerRect" Grid.Column="1" Grid.Row="4" IsReadOnly="True" />
+
+      <Label Grid.Column="0" Grid.Row="5">Screen Rect</Label>
+      <TextBox Name="CurrentScreenRect" Grid.Column="1" Grid.Row="5" IsReadOnly="True" />
+
+      <Label Grid.Column="0" Grid.Row="6">Scaling</Label>
+      <TextBox Name="CurrentScaling" Grid.Column="1" Grid.Row="6" IsReadOnly="True" />
+
+      <Label Grid.Column="0" Grid.Row="7">WindowState</Label>
+      <ComboBox Name="CurrentWindowState" Grid.Column="1" Grid.Row="7" SelectedIndex="{Binding WindowState}">
+        <ComboBoxItem Name="WindowStateNormal">Normal</ComboBoxItem>
+        <ComboBoxItem Name="WindowStateMinimized">Minimized</ComboBoxItem>
+        <ComboBoxItem Name="WindowStateMaximized">Maximized</ComboBoxItem>
+        <ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem>
+      </ComboBox>
+
+      <Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>
+      <TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="8" IsReadOnly="True" />
+      
+      <Label Grid.Row="9" Content="MeasuredWith:" />
+      <TextBlock Grid.Column="1" Grid.Row="9" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" />
+
+      <Button Name="HideButton" Grid.Row="10" Command="{Binding $parent[Window].Hide}">Hide</Button>
+      
+    </Grid>
+  </integrationTestApp:MeasureBorder>
 </Window>
 </Window>

+ 25 - 6
samples/IntegrationTestApp/ShowWindowTest.axaml.cs

@@ -7,6 +7,25 @@ using Avalonia.Threading;
 
 
 namespace IntegrationTestApp
 namespace IntegrationTestApp
 {
 {
+    public class MeasureBorder : Border
+    {
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            MeasuredWith = availableSize;
+            
+            return base.MeasureOverride(availableSize);
+        }
+
+        public static readonly StyledProperty<Size> MeasuredWithProperty = AvaloniaProperty.Register<MeasureBorder, Size>(
+            nameof(MeasuredWith));
+
+        public Size MeasuredWith
+        {
+            get => GetValue(MeasuredWithProperty);
+            set => SetValue(MeasuredWithProperty, value);
+        }
+    }
+    
     public class ShowWindowTest : Window
     public class ShowWindowTest : Window
     {
     {
         private readonly DispatcherTimer? _timer;
         private readonly DispatcherTimer? _timer;
@@ -16,11 +35,11 @@ namespace IntegrationTestApp
         {
         {
             InitializeComponent();
             InitializeComponent();
             DataContext = this;
             DataContext = this;
-            PositionChanged += (s, e) => this.GetControl<TextBox>("Position").Text = $"{Position}";
+            PositionChanged += (s, e) => this.GetControl<TextBox>("CurrentPosition").Text = $"{Position}";
 
 
             if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
             if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
             {
             {
-                _orderTextBox = this.GetControl<TextBox>("Order");
+                _orderTextBox = this.GetControl<TextBox>("CurrentOrder");
                 _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
                 _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
                 _timer.Tick += TimerOnTick;
                 _timer.Tick += TimerOnTick;
                 _timer.Start();
                 _timer.Start();
@@ -36,13 +55,13 @@ namespace IntegrationTestApp
         {
         {
             base.OnOpened(e);
             base.OnOpened(e);
             var scaling = PlatformImpl!.DesktopScaling;
             var scaling = PlatformImpl!.DesktopScaling;
-            this.GetControl<TextBox>("Position").Text = $"{Position}";
-            this.GetControl<TextBox>("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
-            this.GetControl<TextBox>("Scaling").Text = $"{scaling}";
+            this.GetControl<TextBox>("CurrentPosition").Text = $"{Position}";
+            this.GetControl<TextBox>("CurrentScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
+            this.GetControl<TextBox>("CurrentScaling").Text = $"{scaling}";
 
 
             if (Owner is not null)
             if (Owner is not null)
             {
             {
-                var ownerRect = this.GetControl<TextBox>("OwnerRect");
+                var ownerRect = this.GetControl<TextBox>("CurrentOwnerRect");
                 var owner = (Window)Owner;
                 var owner = (Window)Owner;
                 ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}";
                 ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}";
             }
             }

+ 9 - 2
samples/IntegrationTestApp/bundle.sh

@@ -1,5 +1,12 @@
 #!/usr/bin/env bash
 #!/usr/bin/env bash
 
 
 cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
 cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
-dotnet restore -r osx-arm64
-dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false
+
+arch="x64"
+
+if [[ $(uname -m) == 'arm64' ]]; then
+arch="arm64"
+fi
+
+dotnet restore -r osx-$arch
+dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-$arch -p:_AvaloniaUseExternalMSBuild=false

+ 1 - 0
samples/MobileSandbox/MobileSandbox.csproj

@@ -28,6 +28,7 @@
     <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
     <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
     <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
     <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />

+ 1 - 0
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@@ -7,6 +7,7 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
   </ItemGroup>
   </ItemGroup>

+ 1 - 0
samples/Previewer/Previewer.csproj

@@ -10,6 +10,7 @@
     <EmbeddedResource Include="**\*.xaml" />
     <EmbeddedResource Include="**\*.xaml" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 

+ 1 - 0
samples/ReactiveUIDemo/ReactiveUIDemo.csproj

@@ -7,6 +7,7 @@
   
   
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
   </ItemGroup>
   </ItemGroup>

+ 1 - 0
samples/RenderDemo/RenderDemo.csproj

@@ -12,6 +12,7 @@
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />

+ 1 - 0
samples/Sandbox/Sandbox.csproj

@@ -10,6 +10,7 @@
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
   </ItemGroup>
   </ItemGroup>
   
   

+ 1 - 0
samples/VirtualizationDemo/VirtualizationDemo.csproj

@@ -5,6 +5,7 @@
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />

+ 15 - 0
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Diagnostics;
 using Android.App;
 using Android.App;
 using Android.Content;
 using Android.Content;
 using Android.Content.PM;
 using Android.Content.PM;
@@ -32,6 +33,9 @@ namespace Avalonia.Android
                 lifetime.View = View;
                 lifetime.View = View;
             }
             }
 
 
+            Window?.ClearFlags(WindowManagerFlags.TranslucentStatus);
+            Window?.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
+
             base.OnCreate(savedInstanceState);
             base.OnCreate(savedInstanceState);
 
 
             SetContentView(View);
             SetContentView(View);
@@ -55,6 +59,17 @@ namespace Avalonia.Android
             }
             }
         }
         }
 
 
+        protected override void OnResume()
+        {
+            base.OnResume();
+
+            // Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
+            if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
+            {
+                Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
+            }
+        }
+
         public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
         public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
 
 
         public override void OnBackPressed()
         public override void OnBackPressed()

+ 6 - 0
src/Android/Avalonia.Android/AvaloniaView.cs

@@ -8,6 +8,7 @@ using Avalonia.Android.Platform;
 using Avalonia.Android.Platform.SkiaPlatform;
 using Avalonia.Android.Platform.SkiaPlatform;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Embedding;
 using Avalonia.Controls.Embedding;
+using Avalonia.Controls.Platform;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Rendering;
 
 
@@ -67,6 +68,11 @@ namespace Avalonia.Android
                 }
                 }
 
 
                 _root.Renderer.Start();
                 _root.Renderer.Start();
+
+                if (_view.TryGetFeature<IInsetsManager>(out var insetsManager) == true)
+                {
+                    (insetsManager as AndroidInsetsManager)?.ApplyStatusBarState();
+                }
             }
             }
             else
             else
             {
             {

+ 235 - 0
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using Android.OS;
+using Android.Views;
+using AndroidX.AppCompat.App;
+using AndroidX.Core.View;
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Platform;
+using static Avalonia.Controls.Platform.IInsetsManager;
+
+namespace Avalonia.Android.Platform
+{
+    internal class AndroidInsetsManager : Java.Lang.Object, IInsetsManager, IOnApplyWindowInsetsListener, ViewTreeObserver.IOnGlobalLayoutListener
+    {
+        private readonly AvaloniaMainActivity _activity;
+        private readonly TopLevelImpl _topLevel;
+        private readonly InsetsAnimationCallback _callback;
+        private bool _displayEdgeToEdge;
+        private bool _usesLegacyLayouts;
+        private bool? _systemUiVisibility;
+        private SystemBarTheme? _statusBarTheme;
+        private bool? _isDefaultSystemBarLightTheme;
+
+        public event EventHandler<SafeAreaChangedArgs> SafeAreaChanged;
+
+        public bool DisplayEdgeToEdge
+        {
+            get => _displayEdgeToEdge; 
+            set
+            {
+                _displayEdgeToEdge = value;
+
+                if(Build.VERSION.SdkInt >= BuildVersionCodes.P)
+                {
+                    _activity.Window.Attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
+                }
+
+                WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
+            }
+        }
+
+        public AndroidInsetsManager(AvaloniaMainActivity activity, TopLevelImpl topLevel)
+        {
+            _activity = activity;
+            _topLevel = topLevel;
+            _callback = new InsetsAnimationCallback(WindowInsetsAnimationCompat.Callback.DispatchModeStop);
+
+            _callback.InsetsManager = this;
+
+            ViewCompat.SetOnApplyWindowInsetsListener(_activity.Window.DecorView, this);
+
+            ViewCompat.SetWindowInsetsAnimationCallback(_activity.Window.DecorView, _callback);
+
+            if(Build.VERSION.SdkInt < BuildVersionCodes.R)
+            {
+                _usesLegacyLayouts = true;
+                _activity.Window.DecorView.ViewTreeObserver.AddOnGlobalLayoutListener(this);
+            }
+
+            DisplayEdgeToEdge = false;
+        }
+
+        public Thickness SafeAreaPadding
+        {
+            get
+            {
+                var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
+
+                if (insets != null)
+                {
+                    var renderScaling = _topLevel.RenderScaling;
+
+                    var inset = insets.GetInsets(
+                        (DisplayEdgeToEdge ?
+                            WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() |
+                            WindowInsetsCompat.Type.DisplayCutout() :
+                            0) | WindowInsetsCompat.Type.Ime());
+                    var navBarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars());
+                    var imeInset = insets.GetInsets(WindowInsetsCompat.Type.Ime());
+
+                    return new Thickness(inset.Left / renderScaling,
+                        inset.Top / renderScaling,
+                        inset.Right / renderScaling,
+                        (imeInset.Bottom > 0 && ((_usesLegacyLayouts && !DisplayEdgeToEdge) || !_usesLegacyLayouts) ?
+                            imeInset.Bottom - navBarInset.Bottom :
+                            inset.Bottom) / renderScaling);
+                }
+
+                return default;
+            }
+        }
+
+        public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
+        {
+            NotifySafeAreaChanged(SafeAreaPadding);
+            return insets;
+        }
+
+        private void NotifySafeAreaChanged(Thickness safeAreaPadding)
+        {
+            SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding));
+        }
+
+        public void OnGlobalLayout()
+        {
+            NotifySafeAreaChanged(SafeAreaPadding);
+        }
+
+        public SystemBarTheme? SystemBarTheme
+        {
+            get
+            {
+                try
+                {
+                    var compat = new WindowInsetsControllerCompat(_activity.Window, _topLevel.View);
+
+                    return compat.AppearanceLightStatusBars ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
+                }
+                catch (Exception _)
+                {
+                    return Controls.Platform.SystemBarTheme.Light;
+                }
+            }
+            set
+            {
+                _statusBarTheme = value;
+
+                var isDefault = _statusBarTheme == null;
+
+                if (!_topLevel.View.IsShown)
+                {
+                    return;
+                }
+
+                var compat = new WindowInsetsControllerCompat(_activity.Window, _topLevel.View);
+
+                if (_isDefaultSystemBarLightTheme == null)
+                {
+                    _isDefaultSystemBarLightTheme = compat.AppearanceLightStatusBars;
+                }
+
+                if (value == null && _isDefaultSystemBarLightTheme != null)
+                {
+                    value = (bool)_isDefaultSystemBarLightTheme ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
+                }
+
+                compat.AppearanceLightStatusBars = value == Controls.Platform.SystemBarTheme.Light;
+                compat.AppearanceLightNavigationBars = value == Controls.Platform.SystemBarTheme.Light;
+
+                AppCompatDelegate.DefaultNightMode = isDefault ? AppCompatDelegate.ModeNightFollowSystem : compat.AppearanceLightStatusBars ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes;
+            }
+        }
+
+        public bool? IsSystemBarVisible
+        {
+            get
+            {
+                if(_activity.Window == null)
+                {
+                    return true;
+                }
+                var compat = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
+
+                return compat?.IsVisible(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars());
+            }
+            set
+            {
+                _systemUiVisibility = value;
+
+                if (!_topLevel.View.IsShown)
+                {
+                    return;
+                }
+
+                var compat = WindowCompat.GetInsetsController(_activity.Window, _topLevel.View);
+
+                if (value == null || value.Value)
+                {
+                    compat?.Show(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars());
+                }
+                else
+                {
+                    compat?.Hide(WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars());
+
+                    if (compat != null)
+                    {
+                        compat.SystemBarsBehavior = WindowInsetsControllerCompat.BehaviorShowTransientBarsBySwipe;
+                    }
+                }
+            }
+        }
+
+        internal void ApplyStatusBarState()
+        {
+            IsSystemBarVisible = _systemUiVisibility;
+            SystemBarTheme = _statusBarTheme;
+        }
+
+        private class InsetsAnimationCallback : WindowInsetsAnimationCompat.Callback
+        {
+            public InsetsAnimationCallback(int dispatchMode) : base(dispatchMode)
+            {
+            }
+
+            public AndroidInsetsManager InsetsManager { get; set; }
+
+            public override WindowInsetsCompat OnProgress(WindowInsetsCompat insets, IList<WindowInsetsAnimationCompat> runningAnimations)
+            {
+                foreach (var anim in runningAnimations)
+                {
+                    if ((anim.TypeMask & WindowInsetsCompat.Type.Ime()) != 0)
+                    {
+                        var renderScaling = InsetsManager._topLevel.RenderScaling;
+
+                        var inset = insets.GetInsets((InsetsManager.DisplayEdgeToEdge ? WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() | WindowInsetsCompat.Type.DisplayCutout() : 0) | WindowInsetsCompat.Type.Ime());
+                        var navBarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars());
+                        var imeInset = insets.GetInsets(WindowInsetsCompat.Type.Ime());
+
+
+                        var bottomPadding = (imeInset.Bottom > 0 && !InsetsManager.DisplayEdgeToEdge ? imeInset.Bottom - navBarInset.Bottom : inset.Bottom);
+                        bottomPadding = (int)(bottomPadding * anim.InterpolatedFraction);
+
+                        var padding = new Thickness(inset.Left / renderScaling,
+                            inset.Top / renderScaling,
+                            inset.Right / renderScaling,
+                            bottomPadding / renderScaling);
+                        InsetsManager?.NotifySafeAreaChanged(padding);
+                        break;
+                    }
+                }
+                return insets;
+            }
+        }
+    }
+}

+ 24 - 19
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -3,9 +3,7 @@ using System.Collections.Generic;
 using Android.App;
 using Android.App;
 using Android.Content;
 using Android.Content;
 using Android.Graphics;
 using Android.Graphics;
-using Android.OS;
 using Android.Runtime;
 using Android.Runtime;
-using Android.Text;
 using Android.Views;
 using Android.Views;
 using Android.Views.InputMethods;
 using Android.Views.InputMethods;
 using Avalonia.Android.Platform.Specific;
 using Avalonia.Android.Platform.Specific;
@@ -24,11 +22,13 @@ using Avalonia.Platform.Storage;
 using Avalonia.Rendering;
 using Avalonia.Rendering;
 using Avalonia.Rendering.Composition;
 using Avalonia.Rendering.Composition;
 using Java.Lang;
 using Java.Lang;
+using Java.Util;
 using Math = System.Math;
 using Math = System.Math;
 using AndroidRect = Android.Graphics.Rect;
 using AndroidRect = Android.Graphics.Rect;
 using Window = Android.Views.Window;
 using Window = Android.Views.Window;
 using Android.Graphics.Drawables;
 using Android.Graphics.Drawables;
-using Java.Util;
+using Android.OS;
+using Android.Text;
 
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
 {
@@ -43,6 +43,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         private readonly INativeControlHostImpl _nativeControlHost;
         private readonly INativeControlHostImpl _nativeControlHost;
         private readonly IStorageProvider _storageProvider;
         private readonly IStorageProvider _storageProvider;
         private readonly ISystemNavigationManagerImpl _systemNavigationManager;
         private readonly ISystemNavigationManagerImpl _systemNavigationManager;
+        private readonly AndroidInsetsManager _insetsManager;
         private ViewImpl _view;
         private ViewImpl _view;
 
 
         public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
         public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
@@ -59,6 +60,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
             MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
                 _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
                 _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
 
 
+            if (avaloniaView.Context is AvaloniaMainActivity mainActivity)
+            {
+                _insetsManager = new AndroidInsetsManager(mainActivity, this);
+            }
+
             _nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
             _nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
             _storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
             _storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
 
 
@@ -70,21 +76,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
 
         public IInputRoot InputRoot { get; private set; }
         public IInputRoot InputRoot { get; private set; }
 
 
-        public virtual Size ClientSize
-        {
-            get
-            {
-                AndroidRect rect = new AndroidRect();
-                AndroidRect intersection = new AndroidRect();
-
-                _view.GetWindowVisibleDisplayFrame(intersection);
-                _view.GetGlobalVisibleRect(rect);
-
-                intersection.Intersect(rect);
-
-                return new PixelSize(intersection.Right - intersection.Left, intersection.Bottom - intersection.Top).ToSize(RenderScaling);
-            }
-        }
+        public virtual Size ClientSize => _view.Size.ToSize(RenderScaling);
 
 
         public Size? FrameSize => null;
         public Size? FrameSize => null;
 
 
@@ -285,7 +277,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
 
         public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
         public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
         {
         {
-            // TODO adjust status bar depending on full screen mode.
+            if(_insetsManager != null)
+            {
+                _insetsManager.SystemBarTheme = themeVariant switch
+                {
+                    PlatformThemeVariant.Light => SystemBarTheme.Light,
+                    PlatformThemeVariant.Dark => SystemBarTheme.Dark,
+                    _ => null,
+                };
+            }
         }
         }
 
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
@@ -403,6 +403,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
                 return _nativeControlHost;
                 return _nativeControlHost;
             }
             }
 
 
+            if (featureType == typeof(IInsetsManager))
+            {
+                return _insetsManager;
+            }
+
             return null;
             return null;
         }
         }
     }
     }

+ 6 - 1
src/Avalonia.Base/AttachedProperty.cs

@@ -32,9 +32,14 @@ namespace Avalonia
         /// </summary>
         /// </summary>
         /// <typeparam name="TOwner">The owner type.</typeparam>
         /// <typeparam name="TOwner">The owner type.</typeparam>
         /// <returns>The property.</returns>
         /// <returns>The property.</returns>
-        public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : AvaloniaObject
+        public new AttachedProperty<TValue> AddOwner<TOwner>(StyledPropertyMetadata<TValue>? metadata = null) where TOwner : AvaloniaObject
         {
         {
             AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
             AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
+            if (metadata != null)
+            {
+                OverrideMetadata<TOwner>(metadata);
+            }
+            
             return this;
             return this;
         }
         }
     }
     }

+ 7 - 8
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@@ -2,7 +2,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
-using Avalonia.Styling;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Input.GestureRecognizers
 namespace Avalonia.Input.GestureRecognizers
 {
 {
@@ -11,13 +11,13 @@ namespace Avalonia.Input.GestureRecognizers
         private readonly IInputElement _inputElement;
         private readonly IInputElement _inputElement;
         private List<IGestureRecognizer>? _recognizers;
         private List<IGestureRecognizer>? _recognizers;
         private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
         private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
-        
-        
+
+
         public GestureRecognizerCollection(IInputElement inputElement)
         public GestureRecognizerCollection(IInputElement inputElement)
         {
         {
             _inputElement = inputElement;
             _inputElement = inputElement;
         }
         }
-        
+
         public void Add(IGestureRecognizer recognizer)
         public void Add(IGestureRecognizer recognizer)
         {
         {
             if (_recognizers == null)
             if (_recognizers == null)
@@ -31,14 +31,13 @@ namespace Avalonia.Input.GestureRecognizers
             recognizer.Initialize(_inputElement, this);
             recognizer.Initialize(_inputElement, this);
 
 
             // Hacks to make bindings work
             // Hacks to make bindings work
-            
+
             if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical)
             if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical)
             {
             {
                 logical.SetParent(logicalParent);
                 logical.SetParent(logicalParent);
                 if (recognizer is StyledElement styleableRecognizer
                 if (recognizer is StyledElement styleableRecognizer
                     && _inputElement is StyledElement styleableParent)
                     && _inputElement is StyledElement styleableParent)
-                    styleableRecognizer.Bind(StyledElement.TemplatedParentProperty,
-                        styleableParent.GetObservable(StyledElement.TemplatedParentProperty));
+                    styleableParent.GetObservable(StyledElement.TemplatedParentProperty).Subscribe(parent => styleableRecognizer.TemplatedParent = parent);
             }
             }
         }
         }
 
 
@@ -58,7 +57,7 @@ namespace Avalonia.Input.GestureRecognizers
                 return false;
                 return false;
             foreach (var r in _recognizers)
             foreach (var r in _recognizers)
             {
             {
-                if(e.Handled)
+                if (e.Handled)
                     break;
                     break;
                 r.PointerPressed(e);
                 r.PointerPressed(e);
             }
             }

+ 4 - 8
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@@ -13,22 +13,18 @@ namespace Avalonia.Input
         private Point _initialPosition;
         private Point _initialPosition;
         private int _gestureId;
         private int _gestureId;
         private IPointer? _tracking;
         private IPointer? _tracking;
-        private PullDirection _pullDirection;
         private bool _pullInProgress;
         private bool _pullInProgress;
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="PullDirection"/> property.
         /// Defines the <see cref="PullDirection"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<PullGestureRecognizer, PullDirection> PullDirectionProperty =
-            AvaloniaProperty.RegisterDirect<PullGestureRecognizer, PullDirection>(
-                nameof(PullDirection),
-                o => o.PullDirection,
-                (o, v) => o.PullDirection = v);
+        public static readonly StyledProperty<PullDirection> PullDirectionProperty =
+            AvaloniaProperty.Register<PullGestureRecognizer, PullDirection>(nameof(PullDirection));
 
 
         public PullDirection PullDirection
         public PullDirection PullDirection
         {
         {
-            get => _pullDirection;
-            set => SetAndRaise(PullDirectionProperty, ref _pullDirection, value);
+            get => GetValue(PullDirectionProperty);
+            set => SetValue(PullDirectionProperty, value);
         }
         }
 
 
         public PullGestureRecognizer(PullDirection pullDirection)
         public PullGestureRecognizer(PullDirection pullDirection)

+ 18 - 34
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@@ -17,61 +17,45 @@ namespace Avalonia.Input.GestureRecognizers
         private IPointer? _tracking;
         private IPointer? _tracking;
         private IInputElement? _target;
         private IInputElement? _target;
         private IGestureRecognizerActionsDispatcher? _actions;
         private IGestureRecognizerActionsDispatcher? _actions;
-        private bool _canHorizontallyScroll;
-        private bool _canVerticallyScroll;
         private int _gestureId;
         private int _gestureId;
-        private int _scrollStartDistance = 30;
         private Point _pointerPressedPoint;
         private Point _pointerPressedPoint;
         private VelocityTracker? _velocityTracker;
         private VelocityTracker? _velocityTracker;
 
 
         // Movement per second
         // Movement per second
         private Vector _inertia;
         private Vector _inertia;
         private ulong? _lastMoveTimestamp;
         private ulong? _lastMoveTimestamp;
-        private bool _isScrollInertiaEnabled;
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="CanHorizontallyScroll"/> property.
         /// Defines the <see cref="CanHorizontallyScroll"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanHorizontallyScrollProperty =
-            AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>(
-                nameof(CanHorizontallyScroll),
-                o => o.CanHorizontallyScroll,
-                (o, v) => o.CanHorizontallyScroll = v);
+        public static readonly StyledProperty<bool> CanHorizontallyScrollProperty =
+            AvaloniaProperty.Register<ScrollGestureRecognizer, bool>(nameof(CanHorizontallyScroll));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="CanVerticallyScroll"/> property.
         /// Defines the <see cref="CanVerticallyScroll"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanVerticallyScrollProperty =
-            AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>(
-                nameof(CanVerticallyScroll),
-                o => o.CanVerticallyScroll,
-                (o, v) => o.CanVerticallyScroll = v);
+        public static readonly StyledProperty<bool> CanVerticallyScrollProperty =
+            AvaloniaProperty.Register<ScrollGestureRecognizer, bool>(nameof(CanVerticallyScroll));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsScrollInertiaEnabled"/> property.
         /// Defines the <see cref="IsScrollInertiaEnabled"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<ScrollGestureRecognizer, bool> IsScrollInertiaEnabledProperty =
-            AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>(
-                nameof(IsScrollInertiaEnabled),
-                o => o.IsScrollInertiaEnabled,
-                (o, v) => o.IsScrollInertiaEnabled = v);
+        public static readonly StyledProperty<bool> IsScrollInertiaEnabledProperty =
+            AvaloniaProperty.Register<ScrollGestureRecognizer, bool>(nameof(IsScrollInertiaEnabled));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="ScrollStartDistance"/> property.
         /// Defines the <see cref="ScrollStartDistance"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<ScrollGestureRecognizer, int> ScrollStartDistanceProperty =
-            AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, int>(
-                nameof(ScrollStartDistance),
-                o => o.ScrollStartDistance,
-                (o, v) => o.ScrollStartDistance = v);
+        public static readonly StyledProperty<int> ScrollStartDistanceProperty =
+            AvaloniaProperty.Register<ScrollGestureRecognizer, int>(nameof(ScrollStartDistance), 30);
         
         
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether the content can be scrolled horizontally.
         /// Gets or sets a value indicating whether the content can be scrolled horizontally.
         /// </summary>
         /// </summary>
         public bool CanHorizontallyScroll
         public bool CanHorizontallyScroll
         {
         {
-            get => _canHorizontallyScroll;
-            set => SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value);
+            get => GetValue(CanHorizontallyScrollProperty);
+            set => SetValue(CanHorizontallyScrollProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -79,8 +63,8 @@ namespace Avalonia.Input.GestureRecognizers
         /// </summary>
         /// </summary>
         public bool CanVerticallyScroll
         public bool CanVerticallyScroll
         {
         {
-            get => _canVerticallyScroll;
-            set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value);
+            get => GetValue(CanVerticallyScrollProperty);
+            set => SetValue(CanVerticallyScrollProperty, value);
         }
         }
         
         
         /// <summary>
         /// <summary>
@@ -88,8 +72,8 @@ namespace Avalonia.Input.GestureRecognizers
         /// </summary>
         /// </summary>
         public bool IsScrollInertiaEnabled
         public bool IsScrollInertiaEnabled
         {
         {
-            get => _isScrollInertiaEnabled;
-            set => SetAndRaise(IsScrollInertiaEnabledProperty, ref _isScrollInertiaEnabled, value);
+            get => GetValue(IsScrollInertiaEnabledProperty);
+            set => SetValue(IsScrollInertiaEnabledProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -97,8 +81,8 @@ namespace Avalonia.Input.GestureRecognizers
         /// </summary>
         /// </summary>
         public int ScrollStartDistance
         public int ScrollStartDistance
         {
         {
-            get => _scrollStartDistance;
-            set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
+            get => GetValue(ScrollStartDistanceProperty);
+            set => SetValue(ScrollStartDistanceProperty, value);
         }
         }
         
         
 
 
@@ -137,8 +121,8 @@ namespace Avalonia.Input.GestureRecognizers
                         
                         
                         // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
                         // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
                         _trackedRootPoint = new Point(
                         _trackedRootPoint = new Point(
-                            _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance),
-                            _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance));
+                            _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance),
+                            _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
 
 
                         _actions!.Capture(e.Pointer, this);
                         _actions!.Capture(e.Pointer, this);
                     }
                     }

+ 99 - 31
src/Avalonia.Base/Media/FontManager.cs

@@ -1,9 +1,11 @@
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
 using Avalonia.Media.Fonts;
 using Avalonia.Media.Fonts;
 using Avalonia.Platform;
 using Avalonia.Platform;
+using Avalonia.Utilities;
 
 
 namespace Avalonia.Media
 namespace Avalonia.Media
 {
 {
@@ -13,9 +15,11 @@ namespace Avalonia.Media
     /// </summary>
     /// </summary>
     public sealed class FontManager
     public sealed class FontManager
     {
     {
-        private readonly ConcurrentDictionary<Typeface, IGlyphTypeface> _glyphTypefaceCache =
-            new ConcurrentDictionary<Typeface, IGlyphTypeface>();
-        private readonly FontFamily _defaultFontFamily;
+        internal static Uri SystemFontsKey = new Uri("fonts:SystemFonts");
+
+        public const string FontCollectionScheme = "fonts";
+
+        private readonly ConcurrentDictionary<Uri, IFontCollection> _fontCollections = new ConcurrentDictionary<Uri, IFontCollection>();
         private readonly IReadOnlyList<FontFallback>? _fontFallbacks;
         private readonly IReadOnlyList<FontFallback>? _fontFallbacks;
 
 
         public FontManager(IFontManagerImpl platformImpl)
         public FontManager(IFontManagerImpl platformImpl)
@@ -33,9 +37,12 @@ namespace Avalonia.Media
                 throw new InvalidOperationException("Default font family name can't be null or empty.");
                 throw new InvalidOperationException("Default font family name can't be null or empty.");
             }
             }
 
 
-            _defaultFontFamily = new FontFamily(DefaultFontFamilyName);
+            AddFontCollection(new SystemFontCollection(this));
         }
         }
 
 
+        /// <summary>
+        /// Get the current font manager instance.
+        /// </summary>
         public static FontManager Current
         public static FontManager Current
         {
         {
             get
             get
@@ -57,11 +64,6 @@ namespace Avalonia.Media
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// 
-        /// </summary>
-        public IFontManagerImpl PlatformImpl { get; }
-
         /// <summary>
         /// <summary>
         ///     Gets the system's default font family's name.
         ///     Gets the system's default font family's name.
         /// </summary>
         /// </summary>
@@ -71,41 +73,109 @@ namespace Avalonia.Media
         }
         }
 
 
         /// <summary>
         /// <summary>
-        ///     Get all installed font family names.
+        ///     Get all system fonts.
         /// </summary>
         /// </summary>
-        /// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
-        public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
-            PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
+        public IFontCollection SystemFonts => _fontCollections[SystemFontsKey];
+
+        internal IFontManagerImpl PlatformImpl { get; }
 
 
         /// <summary>
         /// <summary>
-        ///     Returns a new <see cref="IGlyphTypeface"/>, or an existing one if a matching <see cref="IGlyphTypeface"/> exists.
+        ///     Tries to get a glyph typeface for specified typeface.
         /// </summary>
         /// </summary>
         /// <param name="typeface">The typeface.</param>
         /// <param name="typeface">The typeface.</param>
+        /// <param name="glyphTypeface">The created glyphTypeface</param>
         /// <returns>
         /// <returns>
-        ///     The <see cref="IGlyphTypeface"/>.
+        ///     <c>True</c>, if the <see cref="FontManager"/> could create the glyph typeface, <c>False</c> otherwise.
         /// </returns>
         /// </returns>
-        public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
+        public bool TryGetGlyphTypeface(Typeface typeface, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
         {
         {
-            while (true)
+            glyphTypeface = null;
+
+            var fontFamily = typeface.FontFamily;
+
+            if (fontFamily.Key is FontFamilyKey key)
             {
             {
-                if (_glyphTypefaceCache.TryGetValue(typeface, out var glyphTypeface))
+                var source = key.Source;
+
+                if (!source.IsAbsoluteUri)
                 {
                 {
-                    return glyphTypeface;
+                    if (key.BaseUri == null)
+                    {
+                        throw new NotSupportedException($"{nameof(key.BaseUri)} can't be null.");
+                    }
+
+                    source = new Uri(key.BaseUri, source);
                 }
                 }
 
 
-                glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface);
+                if (!_fontCollections.TryGetValue(source, out var fontCollection))
+                {
+                    var embeddedFonts = new EmbeddedFontCollection(source, source);
+
+                    embeddedFonts.Initialize(PlatformImpl);
 
 
-                if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
+                    if (embeddedFonts.Count > 0 && _fontCollections.TryAdd(source, embeddedFonts))
+                    {
+                        fontCollection = embeddedFonts;
+                    }
+                }
+
+                if (fontCollection != null && fontCollection.TryGetGlyphTypeface(fontFamily.FamilyNames.PrimaryFamilyName,
+                    typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
                 {
                 {
-                    return glyphTypeface;
+                    return true;
                 }
                 }
 
 
-                if (typeface.FontFamily == _defaultFontFamily)
+                if (!fontFamily.FamilyNames.HasFallbacks)
                 {
                 {
-                   throw new InvalidOperationException($"Could not create glyph typeface for: {typeface.FontFamily.Name}.");
+                    return false;
                 }
                 }
+            }
 
 
-                typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight);
+            foreach (var familyName in fontFamily.FamilyNames)
+            {
+                if (SystemFonts.TryGetGlyphTypeface(familyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface))
+                {
+                    return true;
+                }
+            }
+
+            return SystemFonts.TryGetGlyphTypeface(DefaultFontFamilyName, typeface.Style, typeface.Weight, typeface.Stretch, out glyphTypeface);
+        }
+
+        /// <summary>
+        /// Add a font collection to the manager.
+        /// </summary>
+        /// <param name="fontCollection">The font collection.</param>
+        /// <exception cref="ArgumentException"></exception>
+        /// <remarks>If a font collection's key is already present the collection is replaced.</remarks>
+        public void AddFontCollection(IFontCollection fontCollection)
+        {
+            var key = fontCollection.Key;
+
+            if (!fontCollection.Key.IsFontCollection())
+            {
+                throw new ArgumentException("Font collection Key should follow the fonts: scheme.", nameof(fontCollection));
+            }
+
+            _fontCollections.AddOrUpdate(key, fontCollection, (_, oldCollection) =>
+            {
+                oldCollection.Dispose();
+
+                return fontCollection;
+            });
+
+            fontCollection.Initialize(PlatformImpl);
+        }
+
+        /// <summary>
+        /// Removes the font collection that corresponds to specified key.
+        /// </summary>
+        /// <param name="key">The font collection's key.</param>
+        public void RemoveFontCollection(Uri key)
+        {
+            if (_fontCollections.TryRemove(key, out var fontCollection))
+            {
+                fontCollection.Dispose();
             }
             }
         }
         }
 
 
@@ -123,18 +193,16 @@ namespace Avalonia.Media
         ///     <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
         ///     <c>True</c>, if the <see cref="FontManager"/> could match the character to specified parameters, <c>False</c> otherwise.
         /// </returns>
         /// </returns>
         public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
         public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
-            FontStretch fontStretch,
-            FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface)
+            FontStretch fontStretch, FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface)
         {
         {
-            if(_fontFallbacks != null)
+            if (_fontFallbacks != null)
             {
             {
                 foreach (var fallback in _fontFallbacks)
                 foreach (var fallback in _fontFallbacks)
                 {
                 {
                     typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);
                     typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight, fontStretch);
 
 
-                    var glyphTypeface = GetOrAddGlyphTypeface(typeface);
-
-                    if(glyphTypeface.TryGetGlyph((uint)codepoint, out _)){
+                    if (TryGetGlyphTypeface(typeface, out var glyphTypeface) && glyphTypeface.TryGetGlyph((uint)codepoint, out _))
+                    {
                         return true;
                         return true;
                     }
                     }
                 }
                 }

+ 290 - 0
src/Avalonia.Base/Media/Fonts/EmbeddedFontCollection.cs

@@ -0,0 +1,290 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Fonts
+{
+    public class EmbeddedFontCollection : IFontCollection
+    {
+        private readonly ConcurrentDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = new();
+
+        private readonly List<FontFamily> _fontFamilies = new List<FontFamily>(1);
+
+        private readonly Uri _key;
+
+        private readonly Uri _source;
+
+        public EmbeddedFontCollection(Uri key, Uri source)
+        {
+            _key = key;
+
+            _source = source;
+        }
+
+        public Uri Key => _key;
+
+        public FontFamily this[int index] => _fontFamilies[index];
+
+        public int Count => _fontFamilies.Count;
+
+        public void Initialize(IFontManagerImpl fontManager)
+        {
+            var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
+
+            var fontAssets = FontFamilyLoader.LoadFontAssets(_source);
+
+            foreach (var fontAsset in fontAssets)
+            {
+                var stream = assetLoader.Open(fontAsset);
+
+                if (fontManager.TryCreateGlyphTypeface(stream, out var glyphTypeface))
+                {
+                    if (!_glyphTypefaceCache.TryGetValue(glyphTypeface.FamilyName, out var glyphTypefaces))
+                    {
+                        glyphTypefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>();
+
+                        if (_glyphTypefaceCache.TryAdd(glyphTypeface.FamilyName, glyphTypefaces))
+                        {
+                            _fontFamilies.Add(new FontFamily(_key, glyphTypeface.FamilyName));
+                        }
+                    }
+
+                    var key = new FontCollectionKey(
+                           glyphTypeface.Style,
+                           glyphTypeface.Weight,
+                           glyphTypeface.Stretch);
+
+                    glyphTypefaces.TryAdd(key, glyphTypeface);
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            foreach (var fontFamily in _fontFamilies)
+            {
+                if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out var glyphTypefaces))
+                {
+                    foreach (var glyphTypeface in glyphTypefaces.Values)
+                    {
+                        glyphTypeface.Dispose();
+                    }
+                }
+            }
+
+            GC.SuppressFinalize(this);
+        }
+
+        public IEnumerator<FontFamily> GetEnumerator() => _fontFamilies.GetEnumerator();
+
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+        public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
+            FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+        {
+            var key = new FontCollectionKey(style, weight, stretch);
+
+            if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
+            {
+                if (TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
+                {
+                    return true;
+                }
+            }
+
+            //Try to find a partially matching font
+            for (var i = 0; i < Count; i++)
+            {
+                var fontFamily = _fontFamilies[i];
+
+                if (fontFamily.Name.ToLower(CultureInfo.InvariantCulture).StartsWith(familyName.ToLower(CultureInfo.InvariantCulture)))
+                {
+                    if (_glyphTypefaceCache.TryGetValue(fontFamily.Name, out glyphTypefaces) &&
+                        TryGetNearestMatch(glyphTypefaces, key, out glyphTypeface))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            glyphTypeface = null;
+
+            return false;
+        }
+
+        private static bool TryGetNearestMatch(
+            ConcurrentDictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces,
+            FontCollectionKey key,
+            [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+        {
+            if (glyphTypefaces.TryGetValue(key, out glyphTypeface))
+            {
+                return true;
+            }
+
+            if (key.Style != FontStyle.Normal)
+            {
+                key = key with { Style = FontStyle.Normal };
+            }
+
+            if (key.Stretch != FontStretch.Normal)
+            {
+                if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
+                {
+                    return true;
+                }
+
+                if (key.Weight != FontWeight.Normal)
+                {
+                    if (TryFindStretchFallback(glyphTypefaces, key with { Weight = FontWeight.Normal }, out glyphTypeface))
+                    {
+                        return true;
+                    }
+                }
+
+                key = key with { Stretch = FontStretch.Normal };
+            }
+
+            if (TryFindWeightFallback(glyphTypefaces, key, out glyphTypeface))
+            {
+                return true;
+            }
+
+            if (TryFindStretchFallback(glyphTypefaces, key, out glyphTypeface))
+            {
+                return true;
+            }
+
+            //Take the first glyph typeface we can find.
+            foreach (var typeface in glyphTypefaces.Values)
+            {
+                glyphTypeface = typeface;
+
+                return true;
+            }
+
+            return false;
+        }
+
+        private static bool TryFindStretchFallback(
+            ConcurrentDictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces,
+            FontCollectionKey key,
+            [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+        {
+            glyphTypeface = null;
+
+            var stretch = (int)key.Stretch;
+
+            if (stretch < 5)
+            {
+                for (var i = 0; stretch + i < 9; i++)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch + i) }, out glyphTypeface))
+                    {
+                        return true;
+                    }
+                }
+            }
+            else
+            {
+                for (var i = 0; stretch - i > 1; i++)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Stretch = (FontStretch)(stretch - i) }, out glyphTypeface))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private static bool TryFindWeightFallback(
+            ConcurrentDictionary<FontCollectionKey, IGlyphTypeface> glyphTypefaces,
+            FontCollectionKey key,
+            [NotNullWhen(true)] out IGlyphTypeface? typeface)
+        {
+            typeface = null;
+            var weight = (int)key.Weight;
+
+            //If the target weight given is between 400 and 500 inclusive          
+            if (weight >= 400 && weight <= 500)
+            {
+                //Look for available weights between the target and 500, in ascending order.
+                for (var i = 0; weight + i <= 500; i += 50)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
+                    {
+                        return true;
+                    }
+                }
+
+                //If no match is found, look for available weights less than the target, in descending order.
+                for (var i = 0; weight - i >= 100; i += 50)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface))
+                    {
+                        return true;
+                    }
+                }
+
+                //If no match is found, look for available weights greater than 500, in ascending order.
+                for (var i = 0; weight + i <= 900; i += 50)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            //If a weight less than 400 is given, look for available weights less than the target, in descending order.           
+            if (weight < 400)
+            {
+                for (var i = 0; weight - i >= 100; i += 50)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface))
+                    {
+                        return true;
+                    }
+                }
+
+                //If no match is found, look for available weights less than the target, in descending order.
+                for (var i = 0; weight + i <= 900; i += 50)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            //If a weight greater than 500 is given, look for available weights greater than the target, in ascending order.
+            if (weight > 500)
+            {
+                for (var i = 0; weight + i <= 900; i += 50)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight + i) }, out typeface))
+                    {
+                        return true;
+                    }
+                }
+
+                //If no match is found, look for available weights less than the target, in descending order.
+                for (var i = 0; weight - i >= 100; i += 50)
+                {
+                    if (glyphTypefaces.TryGetValue(key with { Weight = (FontWeight)(weight - i) }, out typeface))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+    }
+}

+ 4 - 0
src/Avalonia.Base/Media/Fonts/FontCollectionKey.cs

@@ -0,0 +1,4 @@
+namespace Avalonia.Media.Fonts
+{
+    public readonly record struct FontCollectionKey(FontStyle Style, FontWeight Weight, FontStretch Stretch);
+}

+ 32 - 34
src/Avalonia.Base/Media/Fonts/FontFamilyLoader.cs

@@ -11,22 +11,30 @@ namespace Avalonia.Media.Fonts
         /// <summary>
         /// <summary>
         /// Loads all font assets that belong to the specified <see cref="FontFamilyKey"/>
         /// Loads all font assets that belong to the specified <see cref="FontFamilyKey"/>
         /// </summary>
         /// </summary>
-        /// <param name="fontFamilyKey"></param>
+        /// <param name="source"></param>
         /// <returns></returns>
         /// <returns></returns>
-        public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey) =>
-            IsFontTtfOrOtf(fontFamilyKey.Source) ?
-                GetFontAssetsByExpression(fontFamilyKey) :
-                GetFontAssetsBySource(fontFamilyKey);
+        public static IEnumerable<Uri> LoadFontAssets(Uri source)
+        {
+            if (source.IsAvares() || source.IsAbsoluteResm())
+            {
+                return IsFontTtfOrOtf(source) ?
+                    GetFontAssetsByExpression(source) :
+                    GetFontAssetsBySource(source);
+            }
+
+            return Enumerable.Empty<Uri>();
+        }
+
 
 
         /// <summary>
         /// <summary>
         /// Searches for font assets at a given location and returns a quantity of found assets
         /// Searches for font assets at a given location and returns a quantity of found assets
         /// </summary>
         /// </summary>
-        /// <param name="fontFamilyKey"></param>
+        /// <param name="source"></param>
         /// <returns></returns>
         /// <returns></returns>
-        private static IEnumerable<Uri> GetFontAssetsBySource(FontFamilyKey fontFamilyKey)
+        private static IEnumerable<Uri> GetFontAssetsBySource(Uri source)
         {
         {
             var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
             var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
-            var availableAssets = assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri);
+            var availableAssets = assetLoader.GetAssets(source, null);
             return availableAssets.Where(x => IsFontTtfOrOtf(x));
             return availableAssets.Where(x => IsFontTtfOrOtf(x));
         }
         }
 
 
@@ -34,60 +42,50 @@ namespace Avalonia.Media.Fonts
         /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression.
         /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression.
         /// <para>File names can target multiple files with * wildcard. For example "FontFile*.ttf"</para>
         /// <para>File names can target multiple files with * wildcard. For example "FontFile*.ttf"</para>
         /// </summary>
         /// </summary>
-        /// <param name="fontFamilyKey"></param>
+        /// <param name="source"></param>
         /// <returns></returns>
         /// <returns></returns>
-        private static IEnumerable<Uri> GetFontAssetsByExpression(FontFamilyKey fontFamilyKey)
+        private static IEnumerable<Uri> GetFontAssetsByExpression(Uri source)
         {
         {
-            var (fileNameWithoutExtension, extension) = GetFileName(fontFamilyKey, out var location);
-            var filePattern = CreateFilePattern(fontFamilyKey, location, fileNameWithoutExtension);
+            var (fileNameWithoutExtension, extension) = GetFileName(source, out var location);
+            var filePattern = CreateFilePattern(source, location, fileNameWithoutExtension);
 
 
             var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
             var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
-            var availableResources = assetLoader.GetAssets(location, fontFamilyKey.BaseUri);
+            var availableResources = assetLoader.GetAssets(location, null);
 
 
             return availableResources.Where(x => IsContainsFile(x, filePattern, extension));
             return availableResources.Where(x => IsContainsFile(x, filePattern, extension));
         }
         }
 
 
         private static (string fileNameWithoutExtension, string extension) GetFileName(
         private static (string fileNameWithoutExtension, string extension) GetFileName(
-            FontFamilyKey fontFamilyKey, out Uri location)
+            Uri source, out Uri location)
         {
         {
-            if (fontFamilyKey.Source.IsAbsoluteResm())
+            if (source.IsAbsoluteResm())
             {
             {
-                var fileName = GetFileNameAndExtension(fontFamilyKey.Source.GetUnescapeAbsolutePath(), '.');
+                var fileName = GetFileNameAndExtension(source.GetUnescapeAbsolutePath(), '.');
 
 
-                var uriLocation = fontFamilyKey.Source.GetUnescapeAbsoluteUri()
+                var uriLocation = source.GetUnescapeAbsoluteUri()
                     .Replace("." + fileName.fileNameWithoutExtension + fileName.extension, string.Empty);
                     .Replace("." + fileName.fileNameWithoutExtension + fileName.extension, string.Empty);
                 location = new Uri(uriLocation, UriKind.RelativeOrAbsolute);
                 location = new Uri(uriLocation, UriKind.RelativeOrAbsolute);
 
 
                 return fileName;
                 return fileName;
             }
             }
 
 
-            var filename = GetFileNameAndExtension(fontFamilyKey.Source.OriginalString);
+            var filename = GetFileNameAndExtension(source.OriginalString);
             var fullFilename = filename.fileNameWithoutExtension + filename.extension;
             var fullFilename = filename.fileNameWithoutExtension + filename.extension;
 
 
-            if (fontFamilyKey.BaseUri != null)
-            {
-                var relativePath = fontFamilyKey.Source.OriginalString
-                    .Replace(fullFilename, string.Empty);
-
-                location = new Uri(fontFamilyKey.BaseUri, relativePath);
-            }
-            else
-            {
-                var uriString = fontFamilyKey.Source
-                    .GetUnescapeAbsoluteUri()
-                    .Replace(fullFilename, string.Empty);
-                location = new Uri(uriString);
-            }
+            var uriString = source
+                .GetUnescapeAbsoluteUri()
+                .Replace(fullFilename, string.Empty);
+            location = new Uri(uriString);
 
 
             return filename;
             return filename;
         }
         }
 
 
         private static string CreateFilePattern(
         private static string CreateFilePattern(
-            FontFamilyKey fontFamilyKey, Uri location, string fileNameWithoutExtension)
+            Uri source, Uri location, string fileNameWithoutExtension)
         {
         {
             var path = location.GetUnescapeAbsolutePath();
             var path = location.GetUnescapeAbsolutePath();
             var file = GetSubString(fileNameWithoutExtension, '*');
             var file = GetSubString(fileNameWithoutExtension, '*');
-            return fontFamilyKey.Source.IsAbsoluteResm()
+            return source.IsAbsoluteResm()
                 ? path + "." + file
                 ? path + "." + file
                 : path + file;
                 : path + file;
         }
         }

+ 33 - 0
src/Avalonia.Base/Media/Fonts/IFontCollection.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Fonts
+{
+    public interface IFontCollection : IReadOnlyList<FontFamily>, IDisposable
+    {
+        /// <summary>
+        /// Get the font collection's key.
+        /// </summary>
+        Uri Key { get; }
+
+        /// <summary>
+        /// Initializes the font collection.
+        /// </summary>
+        /// <param name="fontManager">The font manager the collection is registered with.</param>
+        void Initialize(IFontManagerImpl fontManager);
+
+        /// <summary>
+        /// Try to get a glyph typeface for given parameters.
+        /// </summary>
+        /// <param name="familyName">The family name.</param>
+        /// <param name="style">The font style.</param>
+        /// <param name="weight">The font weight.</param>
+        /// <param name="stretch">The font stretch.</param>
+        /// <param name="glyphTypeface">The glyph typeface.</param>
+        /// <returns>Returns <c>true</c> if a glyph typface can be found; otherwise, <c>false</c></returns>
+        bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
+            FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface);
+    }
+}

+ 107 - 0
src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Fonts
+{
+    internal class SystemFontCollection : IFontCollection
+    {
+        private readonly ConcurrentDictionary<string, ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>> _glyphTypefaceCache = new();
+
+        private readonly FontManager _fontManager;
+        private readonly string[] _familyNames;
+
+        public SystemFontCollection(FontManager fontManager)
+        {
+            _fontManager = fontManager;
+            _familyNames = fontManager.PlatformImpl.GetInstalledFontFamilyNames();
+        }
+
+        public Uri Key => FontManager.SystemFontsKey;
+
+        public FontFamily this[int index]
+        {
+            get
+            {
+                var familyName = _familyNames[index];
+
+                return new FontFamily(familyName);
+            }
+        }
+
+        public int Count => _familyNames.Length;
+
+        public bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
+            FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
+        {
+            if (familyName == FontFamily.DefaultFontFamilyName)
+            {
+                familyName = _fontManager.DefaultFontFamilyName;
+            }
+
+            var key = new FontCollectionKey(style, weight, stretch);
+
+            if (_glyphTypefaceCache.TryGetValue(familyName, out var glyphTypefaces))
+            {
+                if (glyphTypefaces.TryGetValue(key, out glyphTypeface))
+                {
+                    return true;
+                }
+                else
+                {
+                    if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface) &&
+                        glyphTypefaces.TryAdd(key, glyphTypeface))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            if (_fontManager.PlatformImpl.TryCreateGlyphTypeface(familyName, style, weight, stretch, out glyphTypeface))
+            {
+                glyphTypefaces = new ConcurrentDictionary<FontCollectionKey, IGlyphTypeface>();
+
+                if (glyphTypefaces.TryAdd(key, glyphTypeface) && _glyphTypefaceCache.TryAdd(familyName, glyphTypefaces))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public void Initialize(IFontManagerImpl fontManager)
+        {
+            //We initialize the system font collection during construction.
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public IEnumerator<FontFamily> GetEnumerator()
+        {
+            foreach (var familyName in _familyNames)
+            {
+                yield return new FontFamily(familyName);
+            }
+        }
+
+        void IDisposable.Dispose()
+        {
+            foreach (var glyphTypefaces in _glyphTypefaceCache.Values)
+            {
+                foreach (var pair in glyphTypefaces)
+                {
+                    pair.Value.Dispose();
+                }
+            }
+
+            GC.SuppressFinalize(this);
+        }
+    }
+}

+ 7 - 7
src/Avalonia.Base/Media/GlyphRun.cs

@@ -151,9 +151,9 @@ namespace Avalonia.Media
         }
         }
 
 
         /// <summary>
         /// <summary>
-        ///     Gets or sets the conservative bounding box of the <see cref="GlyphRun"/>.
+        ///     Gets the conservative bounding box of the <see cref="GlyphRun"/>.
         /// </summary>
         /// </summary>
-        public Size Size => PlatformImpl.Item.Size;
+        public Rect Bounds => PlatformImpl.Item.Bounds;
 
 
         /// <summary>
         /// <summary>
         /// 
         /// 
@@ -252,7 +252,7 @@ namespace Avalonia.Media
 
 
                 if (characterIndex > Metrics.LastCluster)
                 if (characterIndex > Metrics.LastCluster)
                 {
                 {
-                    return Size.Width;
+                    return Bounds.Width;
                 }
                 }
 
 
                 var glyphIndex = FindGlyphIndex(characterIndex);
                 var glyphIndex = FindGlyphIndex(characterIndex);
@@ -287,7 +287,7 @@ namespace Avalonia.Media
 
 
                 if (characterIndex <= Metrics.FirstCluster)
                 if (characterIndex <= Metrics.FirstCluster)
                 {
                 {
-                    return Size.Width;
+                    return Bounds.Width;
                 }
                 }
 
 
                 for (var i = glyphIndex + 1; i < _glyphInfos.Count; i++)
                 for (var i = glyphIndex + 1; i < _glyphInfos.Count; i++)
@@ -295,7 +295,7 @@ namespace Avalonia.Media
                     distance += _glyphInfos[i].GlyphAdvance;
                     distance += _glyphInfos[i].GlyphAdvance;
                 }
                 }
 
 
-                return Size.Width - distance;
+                return Bounds.Width - distance;
             }
             }
         }
         }
 
 
@@ -321,7 +321,7 @@ namespace Avalonia.Media
             }
             }
 
 
             //After
             //After
-            if (distance >= Size.Width)
+            if (distance >= Bounds.Width)
             {
             {
                 isInside = false;
                 isInside = false;
 
 
@@ -354,7 +354,7 @@ namespace Avalonia.Media
             }
             }
             else
             else
             {
             {
-                currentX = Size.Width;
+                currentX = Bounds.Width;
 
 
                 for (var index = _glyphInfos.Count - 1; index >= 0; index--)
                 for (var index = _glyphInfos.Count - 1; index >= 0; index--)
                 {
                 {

+ 1 - 1
src/Avalonia.Base/Media/GlyphRunDrawing.cs

@@ -32,7 +32,7 @@
 
 
         public override Rect GetBounds()
         public override Rect GetBounds()
         {
         {
-            return GlyphRun != null ? new Rect(GlyphRun.Size) : default;
+            return GlyphRun != null ? GlyphRun.Bounds : default;
         }
         }
     }
     }
 }
 }

+ 20 - 0
src/Avalonia.Base/Media/IGlyphTypeface.cs

@@ -6,6 +6,26 @@ namespace Avalonia.Media
     [Unstable]
     [Unstable]
     public interface IGlyphTypeface : IDisposable
     public interface IGlyphTypeface : IDisposable
     {
     {
+        /// <summary>
+        /// Gets the family name for the <see cref="IGlyphTypeface"/> object.
+        /// </summary>
+        string FamilyName { get; }
+
+        /// <summary>
+        /// Gets the designed weight of the font represented by the <see cref="IGlyphTypeface"/> object.
+        /// </summary>
+        FontWeight Weight { get; }
+
+        /// <summary>
+        /// Gets the style for the <see cref="IGlyphTypeface"/> object.
+        /// </summary>
+        FontStyle Style { get; }
+
+        /// <summary>
+        /// Gets the <see cref="FontStretch"/> value for the <see cref="IGlyphTypeface"/> object.
+        /// </summary>
+        FontStretch Stretch { get; }
+
         /// <summary>
         /// <summary>
         ///     Gets the number of glyphs held by this glyph typeface. 
         ///     Gets the number of glyphs held by this glyph typeface. 
         /// </summary>
         /// </summary>

+ 0 - 2
src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs

@@ -48,8 +48,6 @@ namespace Avalonia.Media.Imaging
 
 
         public CroppedBitmap()
         public CroppedBitmap()
         {
         {
-            Source = null;
-            SourceRect = default;
         }
         }
 
 
         public CroppedBitmap(IImage source, PixelRect sourceRect)
         public CroppedBitmap(IImage source, PixelRect sourceRect)

+ 1 - 1
src/Avalonia.Base/Media/TextDecoration.cs

@@ -223,7 +223,7 @@ namespace Avalonia.Media
                 if (intersections.Count > 0)
                 if (intersections.Count > 0)
                 {
                 {
                     var last = baselineOrigin.X;
                     var last = baselineOrigin.X;
-                    var finalPos = last + glyphRun.Size.Width;
+                    var finalPos = last + glyphRun.Bounds.Width;
                     var end = last;
                     var end = last;
 
 
                     var points = new List<double>();
                     var points = new List<double>();

+ 2 - 2
src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs

@@ -38,14 +38,14 @@ namespace Avalonia.Media.TextFormatting
 
 
         public override double Baseline => -TextMetrics.Ascent;
         public override double Baseline => -TextMetrics.Ascent;
 
 
-        public override Size Size => GlyphRun.Size;
+        public override Size Size => GlyphRun.Bounds.Size;
 
 
         public GlyphRun GlyphRun => _glyphRun ??= CreateGlyphRun();
         public GlyphRun GlyphRun => _glyphRun ??= CreateGlyphRun();
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public override void Draw(DrawingContext drawingContext, Point origin)
         public override void Draw(DrawingContext drawingContext, Point origin)
         {
         {
-            using (drawingContext.PushPreTransform(Matrix.CreateTranslation(origin)))
+            using (drawingContext.PushTransform(Matrix.CreateTranslation(origin)))
             {
             {
                 if (GlyphRun.GlyphInfos.Count == 0)
                 if (GlyphRun.GlyphInfos.Count == 0)
                 {
                 {

+ 8 - 7
src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs

@@ -122,13 +122,14 @@ namespace Avalonia.Media.TextFormatting
             if (matchFound)
             if (matchFound)
             {
             {
                 // Fallback found
                 // Fallback found
-                var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
-                                
-                if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
-                {                    
-                    return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
-                        biDiLevel);
-                }                
+                if(fontManager.TryGetGlyphTypeface(fallbackTypeface, out var fallbackGlyphTypeface))
+                {
+                    if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
+                    {
+                        return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
+                            biDiLevel);
+                    }
+                }          
             }
             }
 
 
             // no fallback found
             // no fallback found

+ 1 - 1
src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Media.TextFormatting
             var collapsedLength = 0;
             var collapsedLength = 0;
             var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);
             var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);
 
 
-            if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
+            if (properties.Width < shapedSymbol.GlyphRun.Bounds.Width)
             {
             {
                 //Not enough space to fit in the symbol
                 //Not enough space to fit in the symbol
                 return Array.Empty<TextRun>();
                 return Array.Empty<TextRun>();

+ 1 - 1
src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

@@ -60,7 +60,7 @@ namespace Avalonia.Media.TextFormatting
             var currentWidth = 0.0;
             var currentWidth = 0.0;
             var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, FlowDirection.LeftToRight);
             var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, FlowDirection.LeftToRight);
 
 
-            if (Width < shapedSymbol.GlyphRun.Size.Width)
+            if (Width < shapedSymbol.GlyphRun.Bounds.Width)
             {
             {
                 return Array.Empty<TextRun>();
                 return Array.Empty<TextRun>();
             }
             }

+ 3 - 3
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -423,7 +423,7 @@ namespace Avalonia.Media.TextFormatting
                     {
                     {
                         if (currentGlyphRun != null)
                         if (currentGlyphRun != null)
                         {
                         {
-                            currentDistance -= currentGlyphRun.Size.Width;
+                            currentDistance -= currentGlyphRun.Bounds.Width;
                         }
                         }
 
 
                         return currentDistance + distance;
                         return currentDistance + distance;
@@ -477,7 +477,7 @@ namespace Avalonia.Media.TextFormatting
                         {
                         {
                             if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
                             if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft)
                             {
                             {
-                                distance = currentGlyphRun.Size.Width;
+                                distance = currentGlyphRun.Bounds.Width;
                             }
                             }
 
 
                             return true;
                             return true;
@@ -1483,7 +1483,7 @@ namespace Avalonia.Media.TextFormatting
 
 
                     trailingWhitespaceLength += glyphRunMetrics.TrailingWhitespaceLength;
                     trailingWhitespaceLength += glyphRunMetrics.TrailingWhitespaceLength;
 
 
-                    var whitespaceWidth = glyphRun.Size.Width - glyphRunMetrics.Width;
+                    var whitespaceWidth = glyphRun.Bounds.Width - glyphRunMetrics.Width;
 
 
                     width -= whitespaceWidth;
                     width -= whitespaceWidth;
                 }
                 }

+ 12 - 1
src/Avalonia.Base/Media/Typeface.cs

@@ -80,7 +80,18 @@ namespace Avalonia.Media
         /// <value>
         /// <value>
         /// The glyph typeface.
         /// The glyph typeface.
         /// </value>
         /// </value>
-        public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
+        public IGlyphTypeface GlyphTypeface
+        {
+            get
+            {
+                if(FontManager.Current.TryGetGlyphTypeface(this, out var glyphTypeface))
+                {
+                    return glyphTypeface;
+                }
+
+                throw new InvalidOperationException("Could not create glyphTypeface.");
+            }
+        }
 
 
         public static bool operator !=(Typeface a, Typeface b)
         public static bool operator !=(Typeface a, Typeface b)
         {
         {

+ 104 - 51
src/Avalonia.Base/Platform/AssetLoader.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
@@ -62,7 +63,7 @@ namespace Avalonia.Platform
         /// <returns>True if the asset could be found; otherwise false.</returns>
         /// <returns>True if the asset could be found; otherwise false.</returns>
         public bool Exists(Uri uri, Uri? baseUri = null)
         public bool Exists(Uri uri, Uri? baseUri = null)
         {
         {
-            return GetAsset(uri, baseUri) != null;
+            return TryGetAsset(uri, baseUri, out _);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -94,21 +95,27 @@ namespace Avalonia.Platform
         /// </exception>
         /// </exception>
         public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
         public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
         {
         {
-            var asset = GetAsset(uri, baseUri);
-
-            if (asset == null)
+            if (TryGetAsset(uri, baseUri, out var assetDescriptor))
             {
             {
-                throw new FileNotFoundException($"The resource {uri} could not be found.");
+                return (assetDescriptor.GetStream(), assetDescriptor.Assembly);
             }
             }
 
 
-            return (asset.GetStream(), asset.Assembly);
+            throw new FileNotFoundException($"The resource {uri} could not be found.");
         }
         }
 
 
         public Assembly? GetAssembly(Uri uri, Uri? baseUri)
         public Assembly? GetAssembly(Uri uri, Uri? baseUri)
         {
         {
             if (!uri.IsAbsoluteUri && baseUri != null)
             if (!uri.IsAbsoluteUri && baseUri != null)
+            {
                 uri = new Uri(baseUri, uri);
                 uri = new Uri(baseUri, uri);
-            return GetAssembly(uri)?.Assembly;
+            }
+
+            if (TryGetAssembly(uri, out var assemblyDescriptor))
+            {
+                return assemblyDescriptor.Assembly;
+            }
+
+            return null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -121,99 +128,145 @@ namespace Avalonia.Platform
         {
         {
             if (uri.IsAbsoluteResm())
             if (uri.IsAbsoluteResm())
             {
             {
-                var assembly = GetAssembly(uri);
+                if (!TryGetAssembly(uri, out var assembly))
+                {
+                    assembly = _defaultResmAssembly;
+                }
 
 
                 return assembly?.Resources?
                 return assembly?.Resources?
-                           .Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0)
-                           .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
-                       Enumerable.Empty<Uri>();
+                        .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
+                        .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
+                    Enumerable.Empty<Uri>();
             }
             }
 
 
             uri = uri.EnsureAbsolute(baseUri);
             uri = uri.EnsureAbsolute(baseUri);
+
             if (uri.IsAvares())
             if (uri.IsAvares())
             {
             {
-                var (asm, path) = GetResAsmAndPath(uri);
-                if (asm == null)
+                if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
                 {
                 {
-                    throw new ArgumentException(
-                        "No default assembly, entry assembly or explicit assembly specified; " +
-                        "don't know where to look up for the resource, try specifying assembly explicitly.");
+                    return Enumerable.Empty<Uri>();
                 }
                 }
 
 
-                if (asm.AvaloniaResources == null)
+                if (assembly?.AvaloniaResources == null)
+                {
                     return Enumerable.Empty<Uri>();
                     return Enumerable.Empty<Uri>();
+                }
 
 
-                if (path[path.Length - 1] != '/')
+                if (path.Length > 0 && path[path.Length - 1] != '/')
+                {
                     path += '/';
                     path += '/';
+                }
 
 
-                return asm.AvaloniaResources
+                return assembly.AvaloniaResources
                     .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
                     .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
-                    .Select(x => new Uri($"avares://{asm.Name}{x.Key}"));
+                    .Select(x => new Uri($"avares://{assembly.Name}{x.Key}"));
             }
             }
 
 
             return Enumerable.Empty<Uri>();
             return Enumerable.Empty<Uri>();
         }
         }
-        
-        private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri)
-        {           
+
+        private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
+        {
+            assetDescriptor = null;
+
             if (uri.IsAbsoluteResm())
             if (uri.IsAbsoluteResm())
             {
             {
-                var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
-
-                if (asm == null)
+                if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
                 {
                 {
-                    throw new ArgumentException(
-                        "No default assembly, entry assembly or explicit assembly specified; " +
-                        "don't know where to look up for the resource, try specifying assembly explicitly.");
+                    assembly = _defaultResmAssembly;
                 }
                 }
 
 
-                var resourceKey = uri.AbsolutePath;
-                IAssetDescriptor? rv = null;
-                asm.Resources?.TryGetValue(resourceKey, out rv);
-                return rv;
+                if (assembly?.Resources != null)
+                {
+                    var resourceKey = uri.AbsolutePath;
+
+                    if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
+                    {
+                        return true;
+                    }
+                }
             }
             }
 
 
             uri = uri.EnsureAbsolute(baseUri);
             uri = uri.EnsureAbsolute(baseUri);
 
 
             if (uri.IsAvares())
             if (uri.IsAvares())
             {
             {
-                var (asm, path) = GetResAsmAndPath(uri);
-                if (asm.AvaloniaResources == null)
-                    return null;
-                asm.AvaloniaResources.TryGetValue(path, out var desc);
-                return desc;
+                if (TryGetResAsmAndPath(uri, out var assembly, out var path))
+                {
+                    if (assembly.AvaloniaResources == null)
+                    {
+                        return false;
+                    }
+
+                    if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor))
+                    {
+                        return true;
+                    }
+                }
             }
             }
 
 
-            throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri));
+            return false;
         }
         }
 
 
-        private static (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
+        private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path)
         {
         {
-            var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority);
-            return (asm, uri.GetUnescapeAbsolutePath());
+            path = uri.GetUnescapeAbsolutePath();
+
+            if (TryLoadAssembly(uri.Authority, out assembly))
+            {
+                return true;
+            }
+
+            return false;
         }
         }
-        
-        private static IAssemblyDescriptor? GetAssembly(Uri? uri)
+
+        private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
         {
         {
+            assembly = null;
+
             if (uri != null)
             if (uri != null)
             {
             {
                 if (!uri.IsAbsoluteUri)
                 if (!uri.IsAbsoluteUri)
-                    return null;
-                if (uri.IsAvares())
-                    return GetResAsmAndPath(uri).asm;
+                {
+                    return false;
+                }
+
+                if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
+                {
+                    return true;
+                }
 
 
                 if (uri.IsResm())
                 if (uri.IsResm())
                 {
                 {
                     var assemblyName = uri.GetAssemblyNameFromQuery();
                     var assemblyName = uri.GetAssemblyNameFromQuery();
-                    if (assemblyName.Length > 0)
-                        return s_assemblyDescriptorResolver.GetAssembly(assemblyName);
+
+                    if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly))
+                    {
+                        return true;
+                    }
                 }
                 }
             }
             }
 
 
-            return null;
+            return false;
+        }
+
+        private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
+        {
+            assembly = null;
+
+            try
+            {
+                assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName);
+
+                return true;
+            }
+            catch (Exception) { }
+
+            return false;
         }
         }
 #endif
 #endif
-        
+
         public static void RegisterResUriParsers()
         public static void RegisterResUriParsers()
         {
         {
             if (!UriParser.IsKnownScheme("avares"))
             if (!UriParser.IsKnownScheme("avares"))

+ 23 - 7
src/Avalonia.Base/Platform/IFontManagerImpl.cs

@@ -1,5 +1,6 @@
-using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
+using System.IO;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
 
 
@@ -17,7 +18,7 @@ namespace Avalonia.Platform
         ///     Get all installed fonts in the system.
         ///     Get all installed fonts in the system.
         /// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
         /// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
         /// </summary>
         /// </summary>
-        IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false);
+        string[] GetInstalledFontFamilyNames(bool checkForUpdates = false);
 
 
         /// <summary>
         /// <summary>
         ///     Tries to match a specified character to a typeface that supports specified font properties.
         ///     Tries to match a specified character to a typeface that supports specified font properties.
@@ -37,12 +38,27 @@ namespace Avalonia.Platform
             FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface);
             FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface);
 
 
         /// <summary>
         /// <summary>
-        ///     Creates a glyph typeface.
+        ///     Tries to get a glyph typeface for specified parameters.
         /// </summary>
         /// </summary>
-        /// <param name="typeface">The typeface.</param>
-        /// <returns>0
-        ///     The created glyph typeface. Can be <c>Null</c> if it was not possible to create a glyph typeface.
+        /// <param name="familyName">The family name.</param>
+        /// <param name="style">The font style.</param>
+        /// <param name="weight">The font weiht.</param>
+        /// <param name="stretch">The font stretch.</param>
+        /// <param name="glyphTypeface">The created glyphTypeface</param>
+        /// <returns>
+        ///     <c>True</c>, if the <see cref="IFontManagerImpl"/> could create the glyph typeface, <c>False</c> otherwise.
+        /// </returns>
+        bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
+            FontStretch stretch, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface);
+
+        /// <summary>
+        ///     Tries to create a glyph typeface from specified stream.
+        /// </summary>
+        /// <param name="stream">A stream that holds the font's data.</param>
+        /// <param name="glyphTypeface">The created glyphTypeface</param>
+        /// <returns>
+        ///     <c>True</c>, if the <see cref="IFontManagerImpl"/> could create the glyph typeface, <c>False</c> otherwise.
         /// </returns>
         /// </returns>
-        IGlyphTypeface CreateGlyphTypeface(Typeface typeface);
+        bool TryCreateGlyphTypeface(Stream stream, [NotNullWhen(returnValue: true)] out IGlyphTypeface? glyphTypeface);
     }
     }
 }
 }

+ 1 - 2
src/Avalonia.Base/Platform/IGlyphRunImpl.cs

@@ -10,11 +10,10 @@ namespace Avalonia.Platform
     [Unstable]
     [Unstable]
     public interface IGlyphRunImpl : IDisposable 
     public interface IGlyphRunImpl : IDisposable 
     {
     {
-
         /// <summary>
         /// <summary>
         ///     Gets the conservative bounding box of the glyph run./>.
         ///     Gets the conservative bounding box of the glyph run./>.
         /// </summary>
         /// </summary>
-        Size Size { get; }
+        Rect Bounds { get; }
 
 
         /// <summary>
         /// <summary>
         ///     Gets the baseline origin of the glyph run./>.
         ///     Gets the baseline origin of the glyph run./>.

+ 1 - 1
src/Avalonia.Base/PropertyStore/ValueStore.cs

@@ -924,7 +924,7 @@ namespace Avalonia.PropertyStore
                 {
                 {
                     _effectiveValues.GetKeyValue(i, out var key, out var e);
                     _effectiveValues.GetKeyValue(i, out var key, out var e);
 
 
-                    if (e.Priority == BindingPriority.Unset)
+                    if (e.Priority == BindingPriority.Unset && !e.IsOverridenCurrentValue)
                     {
                     {
                         RemoveEffectiveValue(key, i);
                         RemoveEffectiveValue(key, i);
                         e.DisposeAndRaiseUnset(this, key);
                         e.DisposeAndRaiseUnset(this, key);

+ 4 - 4
src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs

@@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition.Server
 
 
             for (var c = FirstChar; c <= LastChar; c++)
             for (var c = FirstChar; c <= LastChar; c++)
             {
             {
-                var height = _runs[c - FirstChar].Size.Height;
+                var height = _runs[c - FirstChar].Bounds.Height;
                 if (height > maxHeight)
                 if (height > maxHeight)
                 {
                 {
                     maxHeight = height;
                     maxHeight = height;
@@ -51,8 +51,8 @@ namespace Avalonia.Rendering.Composition.Server
             {
             {
                 var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
                 var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
                 var run = _runs[effectiveChar - FirstChar];
                 var run = _runs[effectiveChar - FirstChar];
-                width += run.Size.Width;
-                height = Math.Max(height, run.Size.Height);
+                width += run.Bounds.Width;
+                height = Math.Max(height, run.Bounds.Height);
             }
             }
 
 
             return new Size(width, height);
             return new Size(width, height);
@@ -69,7 +69,7 @@ namespace Avalonia.Rendering.Composition.Server
                 var run = _runs[effectiveChar - FirstChar];
                 var run = _runs[effectiveChar - FirstChar];
                 context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
                 context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
                 context.DrawGlyphRun(foreground, run.PlatformImpl);
                 context.DrawGlyphRun(foreground, run.PlatformImpl);
-                offset += run.Size.Width;
+                offset += run.Bounds.Width;
             }
             }
 
 
             context.Transform = originalTransform;
             context.Transform = originalTransform;

+ 2 - 3
src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs

@@ -16,12 +16,11 @@ namespace Avalonia.Rendering.SceneGraph
         /// <param name="transform">The transform.</param>
         /// <param name="transform">The transform.</param>
         /// <param name="foreground">The foreground brush.</param>
         /// <param name="foreground">The foreground brush.</param>
         /// <param name="glyphRun">The glyph run to draw.</param>
         /// <param name="glyphRun">The glyph run to draw.</param>
-        /// <param name="aux">Auxiliary data required to draw the brush.</param>
         public GlyphRunNode(
         public GlyphRunNode(
             Matrix transform,
             Matrix transform,
             IImmutableBrush foreground,
             IImmutableBrush foreground,
             IRef<IGlyphRunImpl> glyphRun)
             IRef<IGlyphRunImpl> glyphRun)
-            : base(new Rect(glyphRun.Item.Size), transform, foreground)
+            : base(glyphRun.Item.Bounds, transform, foreground)
         {
         {
             GlyphRun = glyphRun.Clone();
             GlyphRun = glyphRun.Clone();
         }
         }
@@ -54,7 +53,7 @@ namespace Avalonia.Rendering.SceneGraph
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override bool HitTest(Point p) => new Rect(GlyphRun.Item.Size).ContainsExclusive(p);
+        public override bool HitTest(Point p) => GlyphRun.Item.Bounds.ContainsExclusive(p);
 
 
         public override void Dispose()
         public override void Dispose()
         {
         {

+ 1 - 2
src/Avalonia.Base/StyledElement.cs

@@ -67,8 +67,7 @@ namespace Avalonia
         public static readonly DirectProperty<StyledElement, AvaloniaObject?> TemplatedParentProperty =
         public static readonly DirectProperty<StyledElement, AvaloniaObject?> TemplatedParentProperty =
             AvaloniaProperty.RegisterDirect<StyledElement, AvaloniaObject?>(
             AvaloniaProperty.RegisterDirect<StyledElement, AvaloniaObject?>(
                 nameof(TemplatedParent),
                 nameof(TemplatedParent),
-                o => o.TemplatedParent,
-                (o ,v) => o.TemplatedParent = v);
+                o => o.TemplatedParent);
         
         
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Theme"/> property.
         /// Defines the <see cref="Theme"/> property.

+ 6 - 1
src/Avalonia.Base/StyledProperty.cs

@@ -56,9 +56,14 @@ namespace Avalonia
         /// </summary>
         /// </summary>
         /// <typeparam name="TOwner">The type of the additional owner.</typeparam>
         /// <typeparam name="TOwner">The type of the additional owner.</typeparam>
         /// <returns>The property.</returns>        
         /// <returns>The property.</returns>        
-        public StyledProperty<TValue> AddOwner<TOwner>() where TOwner : AvaloniaObject
+        public StyledProperty<TValue> AddOwner<TOwner>(StyledPropertyMetadata<TValue>? metadata = null) where TOwner : AvaloniaObject
         {
         {
             AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
             AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
+            if (metadata != null)
+            {
+                OverrideMetadata<TOwner>(metadata);
+            }
+
             return this;
             return this;
         }
         }
 
 

+ 4 - 1
src/Avalonia.Base/Utilities/UriExtensions.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using Avalonia.Media;
 
 
 namespace Avalonia.Utilities;
 namespace Avalonia.Utilities;
 
 
@@ -10,7 +11,9 @@ internal static class UriExtensions
     public static bool IsResm(this Uri uri) => uri.Scheme == "resm";
     public static bool IsResm(this Uri uri) => uri.Scheme == "resm";
     
     
     public static bool IsAvares(this Uri uri) => uri.Scheme == "avares";
     public static bool IsAvares(this Uri uri) => uri.Scheme == "avares";
-    
+
+    public static bool IsFontCollection(this Uri uri) => uri.Scheme == FontManager.FontCollectionScheme;
+
     public static Uri EnsureAbsolute(this Uri uri, Uri? baseUri)
     public static Uri EnsureAbsolute(this Uri uri, Uri? baseUri)
     {
     {
         if (uri.IsAbsoluteUri)
         if (uri.IsAbsoluteUri)

+ 15 - 0
src/Avalonia.Controls/AppBuilder.cs

@@ -4,6 +4,8 @@ using System.Reflection;
 using System.Linq;
 using System.Linq;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Platform;
 using Avalonia.Platform;
+using Avalonia.Media.Fonts;
+using Avalonia.Media;
 
 
 namespace Avalonia
 namespace Avalonia
 {
 {
@@ -205,6 +207,19 @@ namespace Avalonia
             return Self;
             return Self;
         }
         }
         
         
+        /// <summary>
+        /// Registers an action that is executed with the current font manager.
+        /// </summary>
+        /// <param name="action">The action.</param>
+        /// <returns>An <see cref="AppBuilder"/> instance.</returns>
+        public AppBuilder ConfigureFonts(Action<FontManager> action)
+        {
+            return AfterSetup(appBuilder =>
+            {
+                action?.Invoke(FontManager.Current);
+            });
+        }
+
         /// <summary>
         /// <summary>
         /// Sets up the platform-specific services for the <see cref="Application"/>.
         /// Sets up the platform-specific services for the <see cref="Application"/>.
         /// </summary>
         /// </summary>

+ 34 - 0
src/Avalonia.Controls/Automation/Peers/LabelAutomationPeer.cs

@@ -0,0 +1,34 @@
+using Avalonia.Automation.Peers;
+using Avalonia.Controls.Primitives;
+
+namespace Avalonia.Controls.Automation.Peers
+{
+    public class LabelAutomationPeer : ControlAutomationPeer
+    {
+        public LabelAutomationPeer(Label owner) : base(owner)
+        {
+        }
+
+        override protected string GetClassNameCore()
+        {
+            return "Text";
+        }
+
+        override protected AutomationControlType GetAutomationControlTypeCore()
+        {
+            return AutomationControlType.Text;
+        }
+
+        override protected string? GetNameCore()
+        {
+            var content = ((Label)Owner).Content as string;
+
+            if (string.IsNullOrEmpty(content))
+            {
+                return base.GetNameCore();
+            }
+
+            return AccessText.RemoveAccessKeyMarker(content) ?? string.Empty;
+        }
+    }
+}

+ 21 - 24
src/Avalonia.Controls/Button.cs

@@ -1,11 +1,9 @@
 using System;
 using System;
-using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Windows.Input;
 using System.Windows.Input;
 using Avalonia.Automation.Peers;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Templates;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
@@ -48,9 +46,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Command"/> property.
         /// Defines the <see cref="Command"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<Button, ICommand?> CommandProperty =
-            AvaloniaProperty.RegisterDirect<Button, ICommand?>(nameof(Command),
-                button => button.Command, (button, command) => button.Command = command, enableDataValidation: true);
+        public static readonly StyledProperty<ICommand?> CommandProperty =
+            AvaloniaProperty.Register<Button, ICommand?>(nameof(Command), enableDataValidation: true);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="HotKey"/> property.
         /// Defines the <see cref="HotKey"/> property.
@@ -85,8 +82,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsPressed"/> property.
         /// Defines the <see cref="IsPressed"/> property.
         /// </summary>
         /// </summary>
-        public static readonly StyledProperty<bool> IsPressedProperty =
-            AvaloniaProperty.Register<Button, bool>(nameof(IsPressed));
+        public static readonly DirectProperty<Button, bool> IsPressedProperty =
+            AvaloniaProperty.RegisterDirect<Button, bool>(nameof(IsPressed), b => b.IsPressed);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Flyout"/> property
         /// Defines the <see cref="Flyout"/> property
@@ -94,10 +91,10 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<FlyoutBase?> FlyoutProperty =
         public static readonly StyledProperty<FlyoutBase?> FlyoutProperty =
             AvaloniaProperty.Register<Button, FlyoutBase?>(nameof(Flyout));
             AvaloniaProperty.Register<Button, FlyoutBase?>(nameof(Flyout));
 
 
-        private ICommand? _command;
         private bool _commandCanExecute = true;
         private bool _commandCanExecute = true;
         private KeyGesture? _hotkey;
         private KeyGesture? _hotkey;
         private bool _isFlyoutOpen = false;
         private bool _isFlyoutOpen = false;
+        private bool _isPressed = false;
 
 
         /// <summary>
         /// <summary>
         /// Initializes static members of the <see cref="Button"/> class.
         /// Initializes static members of the <see cref="Button"/> class.
@@ -138,8 +135,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public ICommand? Command
         public ICommand? Command
         {
         {
-            get => _command;
-            set => SetAndRaise(CommandProperty, ref _command, value);
+            get => GetValue(CommandProperty);
+            set => SetValue(CommandProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -185,8 +182,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool IsPressed
         public bool IsPressed
         {
         {
-            get => GetValue(IsPressedProperty);
-            private set => SetValue(IsPressedProperty, value);
+            get => _isPressed;
+            private set => SetAndRaise(IsPressedProperty, ref _isPressed, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -248,7 +245,7 @@ namespace Avalonia.Controls
         {
         {
             if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
             if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
             {
             {
-                HotKey = _hotkey;
+                SetCurrentValue(HotKeyProperty, _hotkey);
             }
             }
 
 
             base.OnAttachedToLogicalTree(e);
             base.OnAttachedToLogicalTree(e);
@@ -267,7 +264,7 @@ namespace Avalonia.Controls
             if (HotKey != null)
             if (HotKey != null)
             {
             {
                 _hotkey = HotKey;
                 _hotkey = HotKey;
-                HotKey = null;
+                SetCurrentValue(HotKeyProperty, null);
             }
             }
 
 
             base.OnDetachedFromLogicalTree(e);
             base.OnDetachedFromLogicalTree(e);
@@ -291,17 +288,17 @@ namespace Avalonia.Controls
                     break;
                     break;
 
 
                 case Key.Space:
                 case Key.Space:
-                {
-                    if (ClickMode == ClickMode.Press)
                     {
                     {
-                        OnClick();
+                        if (ClickMode == ClickMode.Press)
+                        {
+                            OnClick();
+                        }
+
+                        IsPressed = true;
+                        e.Handled = true;
+                        break;
                     }
                     }
 
 
-                    IsPressed = true;
-                    e.Handled = true;
-                    break;
-                }
-
                 case Key.Escape when Flyout != null:
                 case Key.Escape when Flyout != null:
                     // If Flyout doesn't have focusable content, close the flyout here
                     // If Flyout doesn't have focusable content, close the flyout here
                     CloseFlyout();
                     CloseFlyout();
@@ -592,7 +589,7 @@ namespace Avalonia.Controls
             {
             {
                 flyout.Opened -= Flyout_Opened;
                 flyout.Opened -= Flyout_Opened;
                 flyout.Closed -= Flyout_Closed;
                 flyout.Closed -= Flyout_Closed;
-             }
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -671,7 +668,7 @@ namespace Avalonia.Controls
         void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
         void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
 
 
         void IClickableControl.RaiseClick() => OnClick();
         void IClickableControl.RaiseClick() => OnClick();
-        
+
         /// <summary>
         /// <summary>
         /// Event handler for when the button's flyout is opened.
         /// Event handler for when the button's flyout is opened.
         /// </summary>
         /// </summary>

+ 37 - 53
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -232,14 +232,9 @@ namespace Avalonia.Controls
         internal const int RowsPerYear = 3;
         internal const int RowsPerYear = 3;
         internal const int ColumnsPerYear = 4;
         internal const int ColumnsPerYear = 4;
 
 
-        private DateTime? _selectedDate;
         private DateTime _selectedMonth;
         private DateTime _selectedMonth;
         private DateTime _selectedYear;
         private DateTime _selectedYear;
 
 
-        private DateTime _displayDate = DateTime.Today;
-        private DateTime? _displayDateStart;
-        private DateTime? _displayDateEnd;
-
         private bool _isShiftPressed;
         private bool _isShiftPressed;
         private bool _displayDateIsChanging;
         private bool _displayDateIsChanging;
 
 
@@ -396,13 +391,13 @@ namespace Avalonia.Controls
                         }
                         }
                     case CalendarMode.Year:
                     case CalendarMode.Year:
                         {
                         {
-                            DisplayDate = SelectedMonth;
+                            SetCurrentValue(DisplayDateProperty, SelectedMonth);
                             SelectedYear = SelectedMonth;
                             SelectedYear = SelectedMonth;
                             break;
                             break;
                         }
                         }
                     case CalendarMode.Decade:
                     case CalendarMode.Decade:
                         {
                         {
-                            DisplayDate = SelectedYear;
+                            SetCurrentValue(DisplayDateProperty, SelectedYear);
                             SelectedMonth = SelectedYear;
                             SelectedMonth = SelectedYear;
                             break;
                             break;
                         }
                         }
@@ -472,7 +467,7 @@ namespace Avalonia.Controls
             if (IsValidSelectionMode(e.NewValue!))
             if (IsValidSelectionMode(e.NewValue!))
             {
             {
                 _displayDateIsChanging = true;
                 _displayDateIsChanging = true;
-                SelectedDate = null;
+                SetCurrentValue(SelectedDateProperty, null);
                 _displayDateIsChanging = false;
                 _displayDateIsChanging = false;
                 SelectedDates.Clear();
                 SelectedDates.Clear();
             }
             }
@@ -497,11 +492,8 @@ namespace Avalonia.Controls
                 || mode == CalendarSelectionMode.None;
                 || mode == CalendarSelectionMode.None;
         }
         }
 
 
-        public static readonly DirectProperty<Calendar, DateTime?> SelectedDateProperty =
-            AvaloniaProperty.RegisterDirect<Calendar, DateTime?>(
-                nameof(SelectedDate),
-                o => o.SelectedDate,
-                (o, v) => o.SelectedDate = v,
+        public static readonly StyledProperty<DateTime?> SelectedDateProperty =
+            AvaloniaProperty.Register<Calendar, DateTime?>(nameof(SelectedDate),
                 defaultBindingMode: BindingMode.TwoWay);
                 defaultBindingMode: BindingMode.TwoWay);
 
 
         /// <summary>
         /// <summary>
@@ -529,8 +521,8 @@ namespace Avalonia.Controls
         /// </remarks>
         /// </remarks>
         public DateTime? SelectedDate
         public DateTime? SelectedDate
         {
         {
-            get { return _selectedDate; }
-            set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); }
+            get => GetValue(SelectedDateProperty);
+            set => SetValue(SelectedDateProperty, value);
         }
         }
         private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e)
         private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e)
         {
         {
@@ -726,11 +718,8 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
-        public static readonly DirectProperty<Calendar, DateTime> DisplayDateProperty =
-            AvaloniaProperty.RegisterDirect<Calendar, DateTime>(
-                nameof(DisplayDate),
-                o => o.DisplayDate,
-                (o, v) => o.DisplayDate = v,
+        public static readonly StyledProperty<DateTime> DisplayDateProperty =
+            AvaloniaProperty.Register<Calendar, DateTime>(nameof(DisplayDate),
                 defaultBindingMode: BindingMode.TwoWay);
                 defaultBindingMode: BindingMode.TwoWay);
 
 
         /// <summary>
         /// <summary>
@@ -760,8 +749,8 @@ namespace Avalonia.Controls
         /// </remarks>
         /// </remarks>
         public DateTime DisplayDate
         public DateTime DisplayDate
         {
         {
-            get { return _displayDate; }
-            set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); }
+            get => GetValue(DisplayDateProperty);
+            set => SetValue(DisplayDateProperty, value);
         }
         }
         internal DateTime DisplayDateInternal { get; private set; }
         internal DateTime DisplayDateInternal { get; private set; }
 
 
@@ -796,11 +785,8 @@ namespace Avalonia.Controls
             DisplayDateChanged?.Invoke(this, e);
             DisplayDateChanged?.Invoke(this, e);
         }
         }
 
 
-        public static readonly DirectProperty<Calendar, DateTime?> DisplayDateStartProperty =
-            AvaloniaProperty.RegisterDirect<Calendar, DateTime?>(
-                nameof(DisplayDateStart),
-                o => o.DisplayDateStart,
-                (o, v) => o.DisplayDateStart = v,
+        public static readonly StyledProperty<DateTime?> DisplayDateStartProperty =
+            AvaloniaProperty.Register<Calendar, DateTime?>(nameof(DisplayDateStart),
                 defaultBindingMode: BindingMode.TwoWay);
                 defaultBindingMode: BindingMode.TwoWay);
         /// <summary>
         /// <summary>
         /// Gets or sets the first date to be displayed.
         /// Gets or sets the first date to be displayed.
@@ -814,8 +800,8 @@ namespace Avalonia.Controls
         /// </remarks>
         /// </remarks>
         public DateTime? DisplayDateStart
         public DateTime? DisplayDateStart
         {
         {
-            get { return _displayDateStart; }
-            set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); }
+            get => GetValue(DisplayDateStartProperty);
+            set => SetValue(DisplayDateStartProperty, value);
         }
         }
         private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
         private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
         {
         {
@@ -831,7 +817,7 @@ namespace Avalonia.Controls
 
 
                     if (selectedDateMin.HasValue && DateTime.Compare(selectedDateMin.Value, newValue.Value) < 0)
                     if (selectedDateMin.HasValue && DateTime.Compare(selectedDateMin.Value, newValue.Value) < 0)
                     {
                     {
-                        DisplayDateStart = selectedDateMin.Value;
+                        SetCurrentValue(DisplayDateStartProperty, selectedDateMin.Value);
                         return;
                         return;
                     }
                     }
 
 
@@ -839,14 +825,14 @@ namespace Avalonia.Controls
                     // DisplayDateEnd = DisplayDateStart
                     // DisplayDateEnd = DisplayDateStart
                     if (DateTime.Compare(newValue.Value, DisplayDateRangeEnd) > 0)
                     if (DateTime.Compare(newValue.Value, DisplayDateRangeEnd) > 0)
                     {
                     {
-                        DisplayDateEnd = DisplayDateStart;
+                        SetCurrentValue(DisplayDateEndProperty, DisplayDateStart);
                     }
                     }
 
 
                     // If DisplayDate < DisplayDateStart,
                     // If DisplayDate < DisplayDateStart,
                     // DisplayDate = DisplayDateStart
                     // DisplayDate = DisplayDateStart
                     if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) > 0)
                     if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) > 0)
                     {
                     {
-                        DisplayDate = newValue.Value;
+                        SetCurrentValue(DisplayDateProperty, newValue.Value);
                     }
                     }
                 }
                 }
                 UpdateMonths();
                 UpdateMonths();
@@ -905,11 +891,8 @@ namespace Avalonia.Controls
             get { return DisplayDateStart.GetValueOrDefault(DateTime.MinValue); }
             get { return DisplayDateStart.GetValueOrDefault(DateTime.MinValue); }
         }
         }
 
 
-        public static readonly DirectProperty<Calendar, DateTime?> DisplayDateEndProperty =
-            AvaloniaProperty.RegisterDirect<Calendar, DateTime?>(
-                nameof(DisplayDateEnd),
-                o => o.DisplayDateEnd,
-                (o, v) => o.DisplayDateEnd = v,
+        public static readonly StyledProperty<DateTime?> DisplayDateEndProperty =
+            AvaloniaProperty.Register<Calendar, DateTime?>(nameof(DisplayDateEnd),
                 defaultBindingMode: BindingMode.TwoWay);
                 defaultBindingMode: BindingMode.TwoWay);
 
 
         /// <summary>
         /// <summary>
@@ -924,8 +907,8 @@ namespace Avalonia.Controls
         /// </remarks>
         /// </remarks>
         public DateTime? DisplayDateEnd
         public DateTime? DisplayDateEnd
         {
         {
-            get { return _displayDateEnd; }
-            set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); }
+            get => GetValue(DisplayDateEndProperty);
+            set => SetValue(DisplayDateEndProperty, value);
         }
         }
 
 
         private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
         private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
@@ -942,7 +925,7 @@ namespace Avalonia.Controls
 
 
                     if (selectedDateMax.HasValue && DateTime.Compare(selectedDateMax.Value, newValue.Value) > 0)
                     if (selectedDateMax.HasValue && DateTime.Compare(selectedDateMax.Value, newValue.Value) > 0)
                     {
                     {
-                        DisplayDateEnd = selectedDateMax.Value;
+                        SetCurrentValue(DisplayDateEndProperty, selectedDateMax.Value);
                         return;
                         return;
                     }
                     }
 
 
@@ -950,7 +933,7 @@ namespace Avalonia.Controls
                     // DisplayDateEnd = DisplayDateStart
                     // DisplayDateEnd = DisplayDateStart
                     if (DateTime.Compare(newValue.Value, DisplayDateRangeStart) < 0)
                     if (DateTime.Compare(newValue.Value, DisplayDateRangeStart) < 0)
                     {
                     {
-                        DisplayDateEnd = DisplayDateStart;
+                        SetCurrentValue(DisplayDateEndProperty, DisplayDateStart);
                         return;
                         return;
                     }
                     }
 
 
@@ -958,7 +941,7 @@ namespace Avalonia.Controls
                     // DisplayDate = DisplayDateEnd
                     // DisplayDate = DisplayDateEnd
                     if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) < 0)
                     if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) < 0)
                     {
                     {
-                        DisplayDate = newValue.Value;
+                        SetCurrentValue(DisplayDateProperty, newValue.Value);
                     }
                     }
                 }
                 }
                 UpdateMonths();
                 UpdateMonths();
@@ -1284,7 +1267,7 @@ namespace Avalonia.Controls
                     {
                     {
                         LastSelectedDate = d.Value;
                         LastSelectedDate = d.Value;
                     }
                     }
-                    DisplayDate = d.Value;
+                    SetCurrentValue(DisplayDateProperty, d.Value);
                 }
                 }
             }
             }
             else
             else
@@ -1332,7 +1315,7 @@ namespace Avalonia.Controls
                     {
                     {
                         LastSelectedDate = d.Value;
                         LastSelectedDate = d.Value;
                     }
                     }
-                    DisplayDate = d.Value;
+                    SetCurrentValue(DisplayDateProperty, d.Value);
                 }
                 }
             }
             }
             else
             else
@@ -1719,7 +1702,7 @@ namespace Avalonia.Controls
                         if (ctrl)
                         if (ctrl)
                         {
                         {
                             SelectedMonth = DisplayDateInternal;
                             SelectedMonth = DisplayDateInternal;
-                            DisplayMode = CalendarMode.Year;
+                            SetCurrentValue(DisplayModeProperty, CalendarMode.Year);
                         }
                         }
                         else
                         else
                         {
                         {
@@ -1733,7 +1716,7 @@ namespace Avalonia.Controls
                         if (ctrl)
                         if (ctrl)
                         {
                         {
                             SelectedYear = SelectedMonth;
                             SelectedYear = SelectedMonth;
-                            DisplayMode = CalendarMode.Decade;
+                            SetCurrentValue(DisplayModeProperty, CalendarMode.Decade);
                         }
                         }
                         else
                         else
                         {
                         {
@@ -1770,8 +1753,8 @@ namespace Avalonia.Controls
                     {
                     {
                         if (ctrl)
                         if (ctrl)
                         {
                         {
-                            DisplayDate = SelectedMonth;
-                            DisplayMode = CalendarMode.Month;
+                            SetCurrentValue(DisplayDateProperty, SelectedMonth);
+                            SetCurrentValue(DisplayModeProperty, CalendarMode.Month);
                         }
                         }
                         else
                         else
                         {
                         {
@@ -1785,7 +1768,7 @@ namespace Avalonia.Controls
                         if (ctrl)
                         if (ctrl)
                         {
                         {
                             SelectedMonth = SelectedYear;
                             SelectedMonth = SelectedYear;
-                            DisplayMode = CalendarMode.Year;
+                            SetCurrentValue(DisplayModeProperty, CalendarMode.Year);
                         }
                         }
                         else
                         else
                         {
                         {
@@ -1850,14 +1833,14 @@ namespace Avalonia.Controls
             {
             {
                 case CalendarMode.Year:
                 case CalendarMode.Year:
                     {
                     {
-                        DisplayDate = SelectedMonth;
-                        DisplayMode = CalendarMode.Month;
+                        SetCurrentValue(DisplayDateProperty, SelectedMonth);
+                        SetCurrentValue(DisplayModeProperty, CalendarMode.Month);
                         return true;
                         return true;
                     }
                     }
                 case CalendarMode.Decade:
                 case CalendarMode.Decade:
                     {
                     {
                         SelectedMonth = SelectedYear;
                         SelectedMonth = SelectedYear;
-                        DisplayMode = CalendarMode.Year;
+                        SetCurrentValue(DisplayModeProperty, CalendarMode.Year);
                         return true;
                         return true;
                     }
                     }
             }
             }
@@ -2103,7 +2086,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public Calendar()
         public Calendar()
         {
         {
-            UpdateDisplayDate(this, this.DisplayDate, DateTime.MinValue);
+            SetCurrentValue(DisplayDateProperty, DateTime.Today);
+            UpdateDisplayDate(this, DisplayDate, DateTime.MinValue);
             BlackoutDates = new CalendarBlackoutDatesCollection(this);
             BlackoutDates = new CalendarBlackoutDatesCollection(this);
             SelectedDates = new SelectedDatesCollection(this);
             SelectedDates = new SelectedDatesCollection(this);
             RemovedItems = new Collection<DateTime>();
             RemovedItems = new Collection<DateTime>();

+ 5 - 9
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -41,7 +41,6 @@ namespace Avalonia.Controls.Primitives
         private Button? _headerButton;
         private Button? _headerButton;
         private Button? _nextButton;
         private Button? _nextButton;
         private Button? _previousButton;
         private Button? _previousButton;
-        private ITemplate<Control>? _dayTitleTemplate;
         
         
         private DateTime _currentMonth;
         private DateTime _currentMonth;
         private bool _isMouseLeftButtonDown;
         private bool _isMouseLeftButtonDown;
@@ -61,17 +60,15 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(HeaderBackgroundProperty, value); }
             set { SetValue(HeaderBackgroundProperty, value); }
         }
         }
 
 
-        public static readonly DirectProperty<CalendarItem, ITemplate<Control>?> DayTitleTemplateProperty =
-                AvaloniaProperty.RegisterDirect<CalendarItem, ITemplate<Control>?>(
+        public static readonly StyledProperty<ITemplate<Control>?> DayTitleTemplateProperty =
+                AvaloniaProperty.Register<CalendarItem, ITemplate<Control>?>(
                     nameof(DayTitleTemplate),
                     nameof(DayTitleTemplate),
-                    o => o.DayTitleTemplate,
-                    (o,v) => o.DayTitleTemplate = v,
                     defaultBindingMode: BindingMode.OneTime);
                     defaultBindingMode: BindingMode.OneTime);
 
 
         public ITemplate<Control>? DayTitleTemplate
         public ITemplate<Control>? DayTitleTemplate
         {
         {
-            get { return _dayTitleTemplate; }
-            set { SetAndRaise(DayTitleTemplateProperty, ref _dayTitleTemplate, value); }
+            get => GetValue(DayTitleTemplateProperty);
+            set => SetValue(DayTitleTemplateProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -176,9 +173,8 @@ namespace Avalonia.Controls.Primitives
 
 
                 for (int i = 0; i < Calendar.RowsPerMonth; i++)
                 for (int i = 0; i < Calendar.RowsPerMonth; i++)
                 {
                 {
-                    if (_dayTitleTemplate != null)
+                    if (DayTitleTemplate?.Build() is Control cell)
                     {
                     {
-                        var cell = _dayTitleTemplate.Build();
                         cell.DataContext = string.Empty;
                         cell.DataContext = string.Empty;
                         cell.SetValue(Grid.RowProperty, 0);
                         cell.SetValue(Grid.RowProperty, 0);
                         cell.SetValue(Grid.ColumnProperty, i);
                         cell.SetValue(Grid.ColumnProperty, i);

+ 27 - 41
src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs

@@ -11,29 +11,22 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DisplayDate"/> property.
         /// Defines the <see cref="DisplayDate"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<CalendarDatePicker, DateTime> DisplayDateProperty =
-            AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime>(
-                nameof(DisplayDate),
-                o => o.DisplayDate,
-                (o, v) => o.DisplayDate = v);
+        public static readonly StyledProperty<DateTime> DisplayDateProperty =
+            AvaloniaProperty.Register<CalendarDatePicker, DateTime>(nameof(DisplayDate));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DisplayDateStart"/> property.
         /// Defines the <see cref="DisplayDateStart"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<CalendarDatePicker, DateTime?> DisplayDateStartProperty =
-            AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>(
-                nameof(DisplayDateStart),
-                o => o.DisplayDateStart,
-                (o, v) => o.DisplayDateStart = v);
+        public static readonly StyledProperty<DateTime?> DisplayDateStartProperty =
+            AvaloniaProperty.Register<CalendarDatePicker, DateTime?>(
+                nameof(DisplayDateStart));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DisplayDateEnd"/> property.
         /// Defines the <see cref="DisplayDateEnd"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<CalendarDatePicker, DateTime?> DisplayDateEndProperty =
-            AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>(
-                nameof(DisplayDateEnd),
-                o => o.DisplayDateEnd,
-                (o, v) => o.DisplayDateEnd = v);
+        public static readonly StyledProperty<DateTime?> DisplayDateEndProperty =
+            AvaloniaProperty.Register<CalendarDatePicker, DateTime?>(
+                nameof(DisplayDateEnd));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="FirstDayOfWeek"/> property.
         /// Defines the <see cref="FirstDayOfWeek"/> property.
@@ -44,11 +37,9 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsDropDownOpen"/> property.
         /// Defines the <see cref="IsDropDownOpen"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<CalendarDatePicker, bool> IsDropDownOpenProperty =
-            AvaloniaProperty.RegisterDirect<CalendarDatePicker, bool>(
-                nameof(IsDropDownOpen),
-                o => o.IsDropDownOpen,
-                (o, v) => o.IsDropDownOpen = v);
+        public static readonly StyledProperty<bool> IsDropDownOpenProperty =
+            AvaloniaProperty.Register<CalendarDatePicker, bool>(
+                nameof(IsDropDownOpen));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsTodayHighlighted"/> property.
         /// Defines the <see cref="IsTodayHighlighted"/> property.
@@ -59,11 +50,9 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="SelectedDate"/> property.
         /// Defines the <see cref="SelectedDate"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<CalendarDatePicker, DateTime?> SelectedDateProperty =
-            AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>(
+        public static readonly StyledProperty<DateTime?> SelectedDateProperty =
+            AvaloniaProperty.Register<CalendarDatePicker, DateTime?>(
                 nameof(SelectedDate),
                 nameof(SelectedDate),
-                o => o.SelectedDate,
-                (o, v) => o.SelectedDate = v,
                 enableDataValidation: true, 
                 enableDataValidation: true, 
                 defaultBindingMode:BindingMode.TwoWay);
                 defaultBindingMode:BindingMode.TwoWay);
 
 
@@ -88,11 +77,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Text"/> property.
         /// Defines the <see cref="Text"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<CalendarDatePicker, string?> TextProperty =
-            AvaloniaProperty.RegisterDirect<CalendarDatePicker, string?>(
-                nameof(Text),
-                o => o.Text,
-                (o, v) => o.Text = v);
+        public static readonly StyledProperty<string?> TextProperty =
+            AvaloniaProperty.Register<CalendarDatePicker, string?>(nameof(Text));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Watermark"/> property.
         /// Defines the <see cref="Watermark"/> property.
@@ -141,8 +127,8 @@ namespace Avalonia.Controls
         /// </exception>
         /// </exception>
         public DateTime DisplayDate
         public DateTime DisplayDate
         {
         {
-            get => _displayDate;
-            set => SetAndRaise(DisplayDateProperty, ref _displayDate, value);
+            get => GetValue(DisplayDateProperty);
+            set => SetValue(DisplayDateProperty, value);
         }
         }
         
         
         /// <summary>
         /// <summary>
@@ -151,8 +137,8 @@ namespace Avalonia.Controls
         /// <value>The first date to display.</value>
         /// <value>The first date to display.</value>
         public DateTime? DisplayDateStart
         public DateTime? DisplayDateStart
         {
         {
-            get => _displayDateStart;
-            set => SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value);
+            get => GetValue(DisplayDateStartProperty);
+            set => SetValue(DisplayDateStartProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -161,8 +147,8 @@ namespace Avalonia.Controls
         /// <value>The last date to display.</value>
         /// <value>The last date to display.</value>
         public DateTime? DisplayDateEnd
         public DateTime? DisplayDateEnd
         {
         {
-            get => _displayDateEnd;
-            set => SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value);
+            get => GetValue(DisplayDateEndProperty);
+            set => SetValue(DisplayDateEndProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -188,8 +174,8 @@ namespace Avalonia.Controls
         /// </value>
         /// </value>
         public bool IsDropDownOpen
         public bool IsDropDownOpen
         {
         {
-            get => _isDropDownOpen;
-            set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value);
+            get => GetValue(IsDropDownOpenProperty);
+            set => SetValue(IsDropDownOpenProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -223,8 +209,8 @@ namespace Avalonia.Controls
         /// </exception>
         /// </exception>
         public DateTime? SelectedDate
         public DateTime? SelectedDate
         {
         {
-            get => _selectedDate;
-            set => SetAndRaise(SelectedDateProperty, ref _selectedDate, value);
+            get => GetValue(SelectedDateProperty);
+            set => SetValue(SelectedDateProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -264,8 +250,8 @@ namespace Avalonia.Controls
         /// </exception>
         /// </exception>
         public string? Text
         public string? Text
         {
         {
-            get => _text;
-            set => SetAndRaise(TextProperty, ref _text, value);
+            get => GetValue(TextProperty);
+            set => SetValue(TextProperty, value);
         }
         }
 
 
         /// <inheritdoc cref="TextBox.Watermark"/>
         /// <inheritdoc cref="TextBox.Watermark"/>

+ 23 - 29
src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs

@@ -45,12 +45,6 @@ namespace Avalonia.Controls
         private DateTime? _onOpenSelectedDate;
         private DateTime? _onOpenSelectedDate;
         private bool _settingSelectedDate;
         private bool _settingSelectedDate;
 
 
-        private DateTime _displayDate;
-        private DateTime? _displayDateStart;
-        private DateTime? _displayDateEnd;
-        private bool _isDropDownOpen;
-        private DateTime? _selectedDate;
-        private string? _text;
         private bool _suspendTextChangeHandler;
         private bool _suspendTextChangeHandler;
         private bool _isPopupClosing;
         private bool _isPopupClosing;
         private bool _ignoreButtonClick;
         private bool _ignoreButtonClick;
@@ -92,9 +86,9 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public CalendarDatePicker()
         public CalendarDatePicker()
         {
         {
-            FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek;
+            SetCurrentValue(FirstDayOfWeekProperty, DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek);
             _defaultText = string.Empty;
             _defaultText = string.Empty;
-            DisplayDate = DateTime.Today;
+            SetCurrentValue(DisplayDateProperty, DateTime.Today);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -257,7 +251,7 @@ namespace Avalonia.Controls
                     Threading.Dispatcher.UIThread.InvokeAsync(() =>
                     Threading.Dispatcher.UIThread.InvokeAsync(() =>
                     {
                     {
                         _settingSelectedDate = true;
                         _settingSelectedDate = true;
-                        Text = DateTimeToString(day);
+                        SetCurrentValue(TextProperty, DateTimeToString(day));
                         _settingSelectedDate = false;
                         _settingSelectedDate = false;
                         OnDateSelected(addedDate, removedDate);
                         OnDateSelected(addedDate, removedDate);
                     });
                     });
@@ -268,7 +262,7 @@ namespace Avalonia.Controls
                     // be changed by the Calendar
                     // be changed by the Calendar
                     if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag))
                     if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag))
                     {
                     {
-                        DisplayDate = day;
+                        SetCurrentValue(DisplayDateProperty, day);
                     }
                     }
 
 
                     if(_calendar != null)
                     if(_calendar != null)
@@ -317,7 +311,7 @@ namespace Avalonia.Controls
                         if (!_settingSelectedDate)
                         if (!_settingSelectedDate)
                         {
                         {
                             _settingSelectedDate = true;
                             _settingSelectedDate = true;
-                            SelectedDate = null;
+                            SetCurrentValue(SelectedDateProperty, null);
                             _settingSelectedDate = false;
                             _settingSelectedDate = false;
                         }
                         }
                     }
                     }
@@ -400,7 +394,7 @@ namespace Avalonia.Controls
                 DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1);
                 DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1);
                 if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value))
                 if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value))
                 {
                 {
-                    SelectedDate = newDate;
+                    SetCurrentValue(SelectedDateProperty, newDate);
                     e.Handled = true;
                     e.Handled = true;
                 }
                 }
             }
             }
@@ -478,7 +472,7 @@ namespace Avalonia.Controls
             {
             {
                 if (SelectedDate.HasValue)
                 if (SelectedDate.HasValue)
                 {
                 {
-                    Text = DateTimeToString(SelectedDate.Value);
+                    SetCurrentValue(TextProperty, DateTimeToString(SelectedDate.Value));
                 }
                 }
                 else if (string.IsNullOrEmpty(_textBox.Text))
                 else if (string.IsNullOrEmpty(_textBox.Text))
                 {
                 {
@@ -491,7 +485,7 @@ namespace Avalonia.Controls
                     if (date != null)
                     if (date != null)
                     {
                     {
                         string? s = DateTimeToString((DateTime)date);
                         string? s = DateTimeToString((DateTime)date);
-                        Text = s;
+                        SetCurrentValue(TextProperty, s);
                     }
                     }
                 }
                 }
             }
             }
@@ -547,7 +541,7 @@ namespace Avalonia.Controls
         private void Calendar_DayButtonMouseUp(object? sender, PointerReleasedEventArgs e)
         private void Calendar_DayButtonMouseUp(object? sender, PointerReleasedEventArgs e)
         {
         {
             Focus();
             Focus();
-            IsDropDownOpen = false;
+            SetCurrentValue(IsDropDownOpenProperty, false);
         }
         }
 
 
         private void Calendar_DisplayDateChanged(object? sender, CalendarDateChangedEventArgs e)
         private void Calendar_DisplayDateChanged(object? sender, CalendarDateChangedEventArgs e)
@@ -564,13 +558,13 @@ namespace Avalonia.Controls
 
 
             if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0]!, SelectedDate.Value) != 0)
             if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0]!, SelectedDate.Value) != 0)
             {
             {
-                SelectedDate = (DateTime?)e.AddedItems[0];
+                SetCurrentValue(SelectedDateProperty, (DateTime?)e.AddedItems[0]);
             }
             }
             else
             else
             {
             {
                 if (e.AddedItems.Count == 0)
                 if (e.AddedItems.Count == 0)
                 {
                 {
-                    SelectedDate = null;
+                    SetCurrentValue(SelectedDateProperty, null);
                     return;
                     return;
                 }
                 }
 
 
@@ -578,7 +572,7 @@ namespace Avalonia.Controls
                 {
                 {
                     if (e.AddedItems.Count > 0)
                     if (e.AddedItems.Count > 0)
                     {
                     {
-                        SelectedDate = (DateTime?)e.AddedItems[0];
+                        SetCurrentValue(SelectedDateProperty, (DateTime?)e.AddedItems[0]);
                     }
                     }
                 }
                 }
             }
             }
@@ -600,18 +594,18 @@ namespace Avalonia.Controls
                 && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape))
                 && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape))
             {
             {
                 Focus();
                 Focus();
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
 
 
                 if (e.Key == Key.Escape)
                 if (e.Key == Key.Escape)
                 {
                 {
-                    SelectedDate = _onOpenSelectedDate;
+                    SetCurrentValue(SelectedDateProperty, _onOpenSelectedDate);
                 }
                 }
             }
             }
         }
         }
 
 
         private void TextBox_GotFocus(object? sender, RoutedEventArgs e)
         private void TextBox_GotFocus(object? sender, RoutedEventArgs e)
         {
         {
-            IsDropDownOpen = false;
+            SetCurrentValue(IsDropDownOpenProperty, false);
         }
         }
 
 
         private void TextBox_KeyDown(object? sender, KeyEventArgs e)
         private void TextBox_KeyDown(object? sender, KeyEventArgs e)
@@ -627,7 +621,7 @@ namespace Avalonia.Controls
             if (_textBox != null)
             if (_textBox != null)
             {
             {
                 _suspendTextChangeHandler = true;
                 _suspendTextChangeHandler = true;
-                Text = _textBox.Text;
+                SetCurrentValue(TextProperty, _textBox.Text);
                 _suspendTextChangeHandler = false;
                 _suspendTextChangeHandler = false;
             }
             }
         }
         }
@@ -660,7 +654,7 @@ namespace Avalonia.Controls
 
 
         private void PopUp_Closed(object? sender, EventArgs e)
         private void PopUp_Closed(object? sender, EventArgs e)
         {
         {
-            IsDropDownOpen = false;
+            SetCurrentValue(IsDropDownOpenProperty, false);
 
 
             if(!_isPopupClosing)
             if(!_isPopupClosing)
             {
             {
@@ -678,12 +672,12 @@ namespace Avalonia.Controls
             if (IsDropDownOpen)
             if (IsDropDownOpen)
             {
             {
                 Focus();
                 Focus();
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
             }
             }
             else
             else
             {
             {
                 SetSelectedDate();
                 SetSelectedDate();
-                IsDropDownOpen = true;
+                SetCurrentValue(IsDropDownOpenProperty, true);
                 _calendar!.Focus();
                 _calendar!.Focus();
             }
             }
         }
         }
@@ -821,14 +815,14 @@ namespace Avalonia.Controls
                     
                     
                     if (SelectedDate != d)
                     if (SelectedDate != d)
                     {
                     {
-                        SelectedDate = d;
+                        SetCurrentValue(SelectedDateProperty, d);
                     }
                     }
                 }
                 }
                 else
                 else
                 {
                 {
                     if (SelectedDate != null)
                     if (SelectedDate != null)
                     {
                     {
-                        SelectedDate = null;
+                        SetCurrentValue(SelectedDateProperty, null);
                     }
                     }
                 }
                 }
             }
             }
@@ -838,7 +832,7 @@ namespace Avalonia.Controls
 
 
                 if (SelectedDate != d)
                 if (SelectedDate != d)
                 {
                 {
-                    SelectedDate = d;
+                    SetCurrentValue(SelectedDateProperty, d);
                 }
                 }
             }
             }
         }
         }
@@ -884,7 +878,7 @@ namespace Avalonia.Controls
                 if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
                 if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
                 {
                 {
                     DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
                     DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
-                    Text = string.Empty;
+                    SetCurrentValue(TextProperty, string.Empty);
                     _defaultText = string.Empty;
                     _defaultText = string.Empty;
                     var watermarkFormat = "<{0}>";
                     var watermarkFormat = "<{0}>";
                     string watermarkText;
                     string watermarkText;

+ 12 - 16
src/Avalonia.Controls/ComboBox.cs

@@ -35,11 +35,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsDropDownOpen"/> property.
         /// Defines the <see cref="IsDropDownOpen"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<ComboBox, bool> IsDropDownOpenProperty =
-            AvaloniaProperty.RegisterDirect<ComboBox, bool>(
-                nameof(IsDropDownOpen),
-                o => o.IsDropDownOpen,
-                (o, v) => o.IsDropDownOpen = v);
+        public static readonly StyledProperty<bool> IsDropDownOpenProperty =
+            AvaloniaProperty.Register<ComboBox, bool>(nameof(IsDropDownOpen));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MaxDropDownHeight"/> property.
         /// Defines the <see cref="MaxDropDownHeight"/> property.
@@ -77,7 +74,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
         public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
             ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
             ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
 
 
-        private bool _isDropDownOpen;
         private Popup? _popup;
         private Popup? _popup;
         private object? _selectionBoxItem;
         private object? _selectionBoxItem;
         private readonly CompositeDisposable _subscriptionsOnOpen = new CompositeDisposable();
         private readonly CompositeDisposable _subscriptionsOnOpen = new CompositeDisposable();
@@ -107,8 +103,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool IsDropDownOpen
         public bool IsDropDownOpen
         {
         {
-            get => _isDropDownOpen;
-            set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value);
+            get => GetValue(IsDropDownOpenProperty);
+            set => SetValue(IsDropDownOpenProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -123,10 +119,10 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Gets or sets the item to display as the control's content.
         /// Gets or sets the item to display as the control's content.
         /// </summary>
         /// </summary>
-        protected object? SelectionBoxItem
+        public object? SelectionBoxItem
         {
         {
             get => _selectionBoxItem;
             get => _selectionBoxItem;
-            set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value);
+            protected set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -191,23 +187,23 @@ namespace Avalonia.Controls
             if ((e.Key == Key.F4 && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == false) ||
             if ((e.Key == Key.F4 && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == false) ||
                 ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt)))
                 ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt)))
             {
             {
-                IsDropDownOpen = !IsDropDownOpen;
+                SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
                 e.Handled = true;
                 e.Handled = true;
             }
             }
             else if (IsDropDownOpen && e.Key == Key.Escape)
             else if (IsDropDownOpen && e.Key == Key.Escape)
             {
             {
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
                 e.Handled = true;
                 e.Handled = true;
             }
             }
             else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space))
             else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space))
             {
             {
-                IsDropDownOpen = true;
+                SetCurrentValue(IsDropDownOpenProperty, true);
                 e.Handled = true;
                 e.Handled = true;
             }
             }
             else if (IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space))
             else if (IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space))
             {
             {
                 SelectFocusedItem();
                 SelectFocusedItem();
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
                 e.Handled = true;
                 e.Handled = true;
             }
             }
             else if (!IsDropDownOpen)
             else if (!IsDropDownOpen)
@@ -291,7 +287,7 @@ namespace Avalonia.Controls
                 }
                 }
                 else
                 else
                 {
                 {
-                    IsDropDownOpen = !IsDropDownOpen;
+                    SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
                     e.Handled = true;
                     e.Handled = true;
                 }
                 }
             }
             }
@@ -390,7 +386,7 @@ namespace Avalonia.Controls
         {
         {
             if (!isVisible && IsDropDownOpen)
             if (!isVisible && IsDropDownOpen)
             {
             {
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
             }
             }
         }
         }
 
 

+ 94 - 89
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@@ -1,7 +1,6 @@
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Shapes;
-using Avalonia.Controls.Templates;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Layout;
@@ -29,65 +28,56 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Define the <see cref="DayFormat"/> Property
         /// Define the <see cref="DayFormat"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, string> DayFormatProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(DayFormat),
-                x => x.DayFormat, (x, v) => x.DayFormat = v);
+        public static readonly StyledProperty<string> DayFormatProperty =
+            AvaloniaProperty.Register<DatePicker, string>(nameof(DayFormat), "%d");
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DayVisible"/> Property
         /// Defines the <see cref="DayVisible"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, bool> DayVisibleProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible),
-                x => x.DayVisible, (x, v) => x.DayVisible = v);
+        public static readonly StyledProperty<bool> DayVisibleProperty =
+            AvaloniaProperty.Register<DatePicker, bool>(nameof(DayVisible), true);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MaxYear"/> Property
         /// Defines the <see cref="MaxYear"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, DateTimeOffset> MaxYearProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MaxYear), 
-                x => x.MaxYear, (x, v) => x.MaxYear = v);
+        public static readonly StyledProperty<DateTimeOffset> MaxYearProperty =
+            AvaloniaProperty.Register<DatePicker, DateTimeOffset>(nameof(MaxYear), DateTimeOffset.MaxValue, coerce: CoerceMaxYear);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MinYear"/> Property
         /// Defines the <see cref="MinYear"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, DateTimeOffset> MinYearProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MinYear), 
-                x => x.MinYear, (x, v) => x.MinYear = v);
+        public static readonly StyledProperty<DateTimeOffset> MinYearProperty =
+            AvaloniaProperty.Register<DatePicker, DateTimeOffset>(nameof(MinYear), DateTimeOffset.MinValue, coerce: CoerceMinYear);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MonthFormat"/> Property
         /// Defines the <see cref="MonthFormat"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, string> MonthFormatProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(MonthFormat), 
-                x => x.MonthFormat, (x, v) => x.MonthFormat = v);
+        public static readonly StyledProperty<string> MonthFormatProperty =
+            AvaloniaProperty.Register<DatePicker, string>(nameof(MonthFormat), "MMMM");
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MonthVisible"/> Property
         /// Defines the <see cref="MonthVisible"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, bool> MonthVisibleProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(MonthVisible), 
-                x => x.MonthVisible, (x, v) => x.MonthVisible = v);
+        public static readonly StyledProperty<bool> MonthVisibleProperty =
+            AvaloniaProperty.Register<DatePicker, bool>(nameof(MonthVisible), true);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="YearFormat"/> Property
         /// Defines the <see cref="YearFormat"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, string> YearFormatProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat), 
-                x => x.YearFormat, (x, v) => x.YearFormat = v);
+        public static readonly StyledProperty<string> YearFormatProperty =
+            AvaloniaProperty.Register<DatePicker, string>(nameof(YearFormat), "yyyy");
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="YearVisible"/> Property
         /// Defines the <see cref="YearVisible"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, bool> YearVisibleProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(YearVisible), 
-                x => x.YearVisible, (x, v) => x.YearVisible = v);
+        public static readonly StyledProperty<bool> YearVisibleProperty =
+            AvaloniaProperty.Register<DatePicker, bool>(nameof(YearVisible), true);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="SelectedDate"/> Property
         /// Defines the <see cref="SelectedDate"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePicker, DateTimeOffset?> SelectedDateProperty =
-            AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), 
-                x => x.SelectedDate, (x, v) => x.SelectedDate = v,
+        public static readonly StyledProperty<DateTimeOffset?> SelectedDateProperty =
+            AvaloniaProperty.Register<DatePicker, DateTimeOffset?>(nameof(SelectedDate),
                 defaultBindingMode: BindingMode.TwoWay);
                 defaultBindingMode: BindingMode.TwoWay);
 
 
         // Template Items
         // Template Items
@@ -103,28 +93,20 @@ namespace Avalonia.Controls
 
 
         private bool _areControlsAvailable;
         private bool _areControlsAvailable;
 
 
-        private string _dayFormat = "%d";
-        private bool _dayVisible = true;
-        private DateTimeOffset _maxYear;
-        private DateTimeOffset _minYear;
-        private string _monthFormat = "MMMM";
-        private bool _monthVisible = true;
-        private string _yearFormat = "yyyy";
-        private bool _yearVisible = true;
-        private DateTimeOffset? _selectedDate;
-
         public DatePicker()
         public DatePicker()
         {
         {
             PseudoClasses.Set(":hasnodate", true);
             PseudoClasses.Set(":hasnodate", true);
             var now = DateTimeOffset.Now;
             var now = DateTimeOffset.Now;
-            _minYear = new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset);
-            _maxYear = new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset);
+            SetCurrentValue(MinYearProperty, new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset));
+            SetCurrentValue(MaxYearProperty, new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset));
         }
         }
 
 
+        private static void OnGridVisibilityChanged(DatePicker sender, AvaloniaPropertyChangedEventArgs e) => sender.SetGrid();
+
         public string DayFormat
         public string DayFormat
         {
         {
-            get => _dayFormat;
-            set => SetAndRaise(DayFormatProperty, ref _dayFormat, value);
+            get => GetValue(DayFormatProperty);
+            set => SetValue(DayFormatProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -132,12 +114,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool DayVisible
         public bool DayVisible
         {
         {
-            get => _dayVisible;
-            set
-            {
-                SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
-                SetGrid();
-            }
+            get => GetValue(DayVisibleProperty);
+            set => SetValue(DayVisibleProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -145,16 +123,24 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public DateTimeOffset MaxYear
         public DateTimeOffset MaxYear
         {
         {
-            get => _maxYear;
-            set
-            {
-                if (value < MinYear)
-                    throw new InvalidOperationException("MaxDate cannot be less than MinDate");
-                SetAndRaise(MaxYearProperty, ref _maxYear, value);
+            get => GetValue(MaxYearProperty);
+            set => SetValue(MaxYearProperty, value);
+        }
 
 
-                if (SelectedDate.HasValue && SelectedDate.Value > value)
-                    SelectedDate = value;
+        private static DateTimeOffset CoerceMaxYear(AvaloniaObject sender, DateTimeOffset value)
+        {
+            if (value < sender.GetValue(MinYearProperty))
+            {
+                throw new InvalidOperationException($"{MaxYearProperty.Name} cannot be less than {MinYearProperty.Name}");
             }
             }
+
+            return value;
+        }
+
+        private void OnMaxYearChanged(DateTimeOffset? value)
+        {
+            if (SelectedDate.HasValue && SelectedDate.Value > value)
+                SetCurrentValue(SelectedDateProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -162,16 +148,24 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public DateTimeOffset MinYear
         public DateTimeOffset MinYear
         {
         {
-            get => _minYear;
-            set
-            {
-                if (value > MaxYear)
-                    throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
-                SetAndRaise(MinYearProperty, ref _minYear, value);
+            get => GetValue(MinYearProperty);
+            set => SetValue(MinYearProperty, value);
+        }
 
 
-                if (SelectedDate.HasValue && SelectedDate.Value < value)
-                    SelectedDate = value;
+        private static DateTimeOffset CoerceMinYear(AvaloniaObject sender, DateTimeOffset value)
+        {
+            if (value > sender.GetValue(MaxYearProperty))
+            {
+                throw new InvalidOperationException($"{MinYearProperty.Name} cannot be greater than {MaxYearProperty.Name}");
             }
             }
+
+            return value;
+        }
+
+        private void OnMinYearChanged(DateTimeOffset? value)
+        {
+            if (SelectedDate.HasValue && SelectedDate.Value < value)
+                SetCurrentValue(SelectedDateProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -179,8 +173,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string MonthFormat
         public string MonthFormat
         {
         {
-            get => _monthFormat;
-            set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value);
+            get => GetValue(MonthFormatProperty);
+            set => SetValue(MonthFormatProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -188,12 +182,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool MonthVisible
         public bool MonthVisible
         {
         {
-            get => _monthVisible;
-            set
-            {
-                SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
-                SetGrid();
-            }
+            get => GetValue(MonthVisibleProperty);
+            set => SetValue(MonthVisibleProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -201,8 +191,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string YearFormat
         public string YearFormat
         {
         {
-            get => _yearFormat;
-            set => SetAndRaise(YearFormatProperty, ref _yearFormat, value);
+            get => GetValue(YearFormatProperty);
+            set => SetValue(YearFormatProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -210,12 +200,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool YearVisible
         public bool YearVisible
         {
         {
-            get => _yearVisible;
-            set
-            {
-                SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
-                SetGrid();
-            }
+            get => GetValue(YearVisibleProperty);
+            set => SetValue(YearVisibleProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -223,14 +209,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public DateTimeOffset? SelectedDate
         public DateTimeOffset? SelectedDate
         {
         {
-            get => _selectedDate;
-            set
-            {
-                var old = _selectedDate;
-                SetAndRaise(SelectedDateProperty, ref _selectedDate, value);
-                SetSelectedDateText();
-                OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(old, value));
-            }
+            get => GetValue(SelectedDateProperty);
+            set => SetValue(SelectedDateProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -287,6 +267,31 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            base.OnPropertyChanged(change);
+
+            if (change.Property == DayVisibleProperty || change.Property == MonthVisibleProperty || change.Property == YearVisibleProperty)
+            {
+                SetGrid();
+            }
+            else if (change.Property == MaxYearProperty)
+            {
+                OnMaxYearChanged(change.GetNewValue<DateTimeOffset>());
+            }
+            else if (change.Property == MinYearProperty)
+            {
+                OnMinYearChanged(change.GetNewValue<DateTimeOffset>());
+            }
+            else if (change.Property == SelectedDateProperty)
+            {
+                SetSelectedDateText();
+
+                var (oldValue, newValue) = change.GetOldAndNewValue<DateTimeOffset?>();
+                OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(oldValue, newValue));
+            }
+        }
+
         private void OnDismissPicker(object? sender, EventArgs e)
         private void OnDismissPicker(object? sender, EventArgs e)
         {
         {
             _popup!.Close();
             _popup!.Close();
@@ -296,7 +301,7 @@ namespace Avalonia.Controls
         private void OnConfirmed(object? sender, EventArgs e)
         private void OnConfirmed(object? sender, EventArgs e)
         {
         {
             _popup!.Close();
             _popup!.Close();
-            SelectedDate = _presenter!.Date;
+            SetCurrentValue(SelectedDateProperty, _presenter!.Date);
         }
         }
 
 
         private void SetGrid()
         private void SetGrid()

+ 89 - 92
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@@ -35,65 +35,72 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Date"/> Property
         /// Defines the <see cref="Date"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> DateProperty =
-            AvaloniaProperty.RegisterDirect<DatePickerPresenter, DateTimeOffset>(nameof(Date), 
-                x => x.Date, (x, v) => x.Date = v);
+        public static readonly StyledProperty<DateTimeOffset> DateProperty =
+            AvaloniaProperty.Register<DatePickerPresenter, DateTimeOffset>(nameof(Date), coerce: CoerceDate);
+
+        private static DateTimeOffset CoerceDate(AvaloniaObject sender, DateTimeOffset value)
+        {
+            var max = sender.GetValue(MaxYearProperty);
+            if (value > max)
+            {
+                return max;
+            }
+            var min = sender.GetValue(MinYearProperty);
+            if (value < min)
+            {
+                return min;
+            }
+
+            return value;
+        }
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DayFormat"/> Property
         /// Defines the <see cref="DayFormat"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, string> DayFormatProperty =
-            DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>(x => 
-            x.DayFormat, (x, v) => x.DayFormat = v);
+        public static readonly StyledProperty<string> DayFormatProperty =
+            DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DayVisible"/> Property
         /// Defines the <see cref="DayVisible"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, bool> DayVisibleProperty =
-            DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>(x => 
-            x.DayVisible, (x, v) => x.DayVisible = v);
+        public static readonly StyledProperty<bool> DayVisibleProperty =
+            DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MaxYear"/> Property
         /// Defines the <see cref="MaxYear"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MaxYearProperty =
-            DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>(x => 
-            x.MaxYear, (x, v) => x.MaxYear = v);
+        public static readonly StyledProperty<DateTimeOffset> MaxYearProperty =
+            DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MinYear"/> Property
         /// Defines the <see cref="MinYear"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MinYearProperty =
-            DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>(x => 
-            x.MinYear, (x, v) => x.MinYear = v);
+        public static readonly StyledProperty<DateTimeOffset> MinYearProperty =
+            DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MonthFormat"/> Property
         /// Defines the <see cref="MonthFormat"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, string> MonthFormatProperty =
-            DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>(x => 
-            x.MonthFormat, (x, v) => x.MonthFormat = v);
+        public static readonly StyledProperty<string> MonthFormatProperty =
+            DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MonthVisible"/> Property
         /// Defines the <see cref="MonthVisible"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, bool> MonthVisibleProperty =
-            DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>(x => 
-            x.MonthVisible, (x, v) => x.MonthVisible = v);
+        public static readonly StyledProperty<bool> MonthVisibleProperty =
+            DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="YearFormat"/> Property
         /// Defines the <see cref="YearFormat"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, string> YearFormatProperty =
-            DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>(x => 
-            x.YearFormat, (x, v) => x.YearFormat = v);
+        public static readonly StyledProperty<string> YearFormatProperty =
+            DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="YearVisible"/> Property
         /// Defines the <see cref="YearVisible"/> Property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<DatePickerPresenter, bool> YearVisibleProperty =
-            DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => 
-            x.YearVisible, (x, v) => x.YearVisible = v);
+        public static readonly StyledProperty<bool> YearVisibleProperty =
+            DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>();
 
 
         // Template Items
         // Template Items
         private Grid? _pickerContainer;
         private Grid? _pickerContainer;
@@ -114,15 +121,6 @@ namespace Avalonia.Controls
         private Button? _dayDownButton;
         private Button? _dayDownButton;
         private Button? _yearDownButton;
         private Button? _yearDownButton;
 
 
-        private DateTimeOffset _date;
-        private string _dayFormat = "%d";
-        private bool _dayVisible = true;
-        private DateTimeOffset _maxYear;
-        private DateTimeOffset _minYear;
-        private string _monthFormat = "MMMM";
-        private bool _monthVisible = true;
-        private string _yearFormat = "yyyy";
-        private bool _yearVisible = true;
         private DateTimeOffset _syncDate;
         private DateTimeOffset _syncDate;
 
 
         private readonly GregorianCalendar _calendar;
         private readonly GregorianCalendar _calendar;
@@ -131,11 +129,20 @@ namespace Avalonia.Controls
         public DatePickerPresenter()
         public DatePickerPresenter()
         {
         {
             var now = DateTimeOffset.Now;
             var now = DateTimeOffset.Now;
-            _minYear = new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset);
-            _maxYear = new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset);
-            _date = now;
+            SetCurrentValue(MinYearProperty, new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset));
+            SetCurrentValue(MaxYearProperty, new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset));
+            SetCurrentValue(DateProperty, now);
             _calendar = new GregorianCalendar();
             _calendar = new GregorianCalendar();
-            KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
+        }
+
+        static DatePickerPresenter()
+        {
+            KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue<DatePickerPresenter>(KeyboardNavigationMode.Cycle);
+        }
+
+        private static void OnDateRangeChanged(DatePickerPresenter sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            sender.CoerceValue(DateProperty);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -143,13 +150,14 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public DateTimeOffset Date
         public DateTimeOffset Date
         {
         {
-            get => _date;
-            set
-            {
-                SetAndRaise(DateProperty, ref _date, value);
-                _syncDate = Date;
-                InitPicker();
-            }
+            get => GetValue(DateProperty);
+            set => SetValue(DateProperty, value);
+        }
+
+        private void OnDateChanged(DateTimeOffset newValue)
+        {
+            _syncDate = newValue;
+            InitPicker();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -157,8 +165,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string DayFormat
         public string DayFormat
         {
         {
-            get => _dayFormat;
-            set => SetAndRaise(DayFormatProperty, ref _dayFormat, value);
+            get => GetValue(DayFormatProperty);
+            set => SetValue(DayFormatProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -166,11 +174,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool DayVisible
         public bool DayVisible
         {
         {
-            get => _dayVisible;
-            set
-            {
-                SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
-            }
+            get => GetValue(DayVisibleProperty);
+            set => SetValue(DayVisibleProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -178,16 +183,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public DateTimeOffset MaxYear
         public DateTimeOffset MaxYear
         {
         {
-            get => _maxYear;
-            set
-            {
-                if (value < MinYear)
-                    throw new InvalidOperationException("MaxDate cannot be less than MinDate");
-                SetAndRaise(MaxYearProperty, ref _maxYear, value);
-
-                if (Date > value)
-                    Date = value;
-            }
+            get => GetValue(MaxYearProperty);
+            set => SetValue(MaxYearProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -195,16 +192,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public DateTimeOffset MinYear
         public DateTimeOffset MinYear
         {
         {
-            get => _minYear;
-            set
-            {
-                if (value > MaxYear)
-                    throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
-                SetAndRaise(MinYearProperty, ref _minYear, value);
-
-                if (Date < value)
-                    Date = value;
-            }
+            get => GetValue(MinYearProperty);
+            set => SetValue(MinYearProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -212,8 +201,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string MonthFormat
         public string MonthFormat
         {
         {
-            get => _monthFormat;
-            set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value);
+            get => GetValue(MonthFormatProperty);
+            set => SetValue(MonthFormatProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -221,11 +210,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool MonthVisible
         public bool MonthVisible
         {
         {
-            get => _monthVisible;
-            set
-            {
-                SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
-            }
+            get => GetValue(MonthVisibleProperty);
+            set => SetValue(MonthVisibleProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -233,8 +219,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string YearFormat
         public string YearFormat
         {
         {
-            get => _yearFormat;
-            set => SetAndRaise(YearFormatProperty, ref _yearFormat, value);
+            get => GetValue(YearFormatProperty);
+            set => SetValue(YearFormatProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -242,11 +228,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool YearVisible
         public bool YearVisible
         {
         {
-            get => _yearVisible;
-            set
-            {
-                SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
-            }
+            get => GetValue(YearVisibleProperty);
+            set => SetValue(YearVisibleProperty, value);
         }
         }
 
 
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -317,6 +300,20 @@ namespace Avalonia.Controls
             InitPicker();
             InitPicker();
         }
         }
 
 
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            base.OnPropertyChanged(change);
+
+            if (change.Property == DateProperty)
+            {
+                OnDateChanged(change.GetNewValue<DateTimeOffset>());
+            }
+            else if (change.Property == MaxYearProperty || change.Property == MinYearProperty)
+            {
+                OnDateRangeChanged(this, change);
+            }
+        }
+
         protected override void OnKeyDown(KeyEventArgs e)
         protected override void OnKeyDown(KeyEventArgs e)
         {
         {
             switch (e.Key)
             switch (e.Key)
@@ -334,7 +331,7 @@ namespace Avalonia.Controls
                     }
                     }
                     break;
                     break;
                 case Key.Enter:
                 case Key.Enter:
-                    Date = _syncDate;
+                    SetCurrentValue(DateProperty, _syncDate);
                     OnConfirmed();
                     OnConfirmed();
                     e.Handled = true;
                     e.Handled = true;
                     break;
                     break;
@@ -381,13 +378,13 @@ namespace Avalonia.Controls
                 _monthSelector.SelectedValue = dt.Month;
                 _monthSelector.SelectedValue = dt.Month;
                 _monthSelector.FormatDate = dt.Date;
                 _monthSelector.FormatDate = dt.Date;
             }
             }
-               
+
             if (YearVisible)
             if (YearVisible)
             {
             {
                 _yearSelector.SelectedValue = dt.Year;
                 _yearSelector.SelectedValue = dt.Year;
                 _yearSelector.FormatDate = dt.Date;
                 _yearSelector.FormatDate = dt.Date;
             }
             }
-                
+
             _suppressUpdateSelection = false;
             _suppressUpdateSelection = false;
 
 
             SetInitialFocus();
             SetInitialFocus();
@@ -471,7 +468,7 @@ namespace Avalonia.Controls
 
 
         private void OnAcceptButtonClicked(object? sender, RoutedEventArgs e)
         private void OnAcceptButtonClicked(object? sender, RoutedEventArgs e)
         {
         {
-            Date = _syncDate;
+            SetCurrentValue(DateProperty, _syncDate);
             OnConfirmed();
             OnConfirmed();
         }
         }
 
 

+ 52 - 41
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@@ -1,7 +1,6 @@
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Shapes;
-using Avalonia.Controls.Templates;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using System;
 using System;
@@ -30,23 +29,20 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MinuteIncrement"/> property
         /// Defines the <see cref="MinuteIncrement"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty =
-            AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement),
-                x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
+        public static readonly StyledProperty<int> MinuteIncrementProperty =
+            AvaloniaProperty.Register<TimePicker, int>(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="ClockIdentifier"/> property
         /// Defines the <see cref="ClockIdentifier"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty =
-           AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier),
-               x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v);
+        public static readonly StyledProperty<string> ClockIdentifierProperty =
+           AvaloniaProperty.Register<TimePicker, string>(nameof(ClockIdentifier), "12HourClock", coerce: CoerceClockIdentifier);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="SelectedTime"/> property
         /// Defines the <see cref="SelectedTime"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty =
-            AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime),
-                x => x.SelectedTime, (x, v) => x.SelectedTime = v,
+        public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
+            AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(SelectedTime),
                 defaultBindingMode: BindingMode.TwoWay);
                 defaultBindingMode: BindingMode.TwoWay);
 
 
         // Template Items
         // Template Items
@@ -63,17 +59,13 @@ namespace Avalonia.Controls
         private Grid? _contentGrid;
         private Grid? _contentGrid;
         private Popup? _popup;
         private Popup? _popup;
 
 
-        private TimeSpan? _selectedTime;
-        private int _minuteIncrement = 1;
-        private string _clockIdentifier = "12HourClock";
-
         public TimePicker()
         public TimePicker()
         {
         {
             PseudoClasses.Set(":hasnotime", true);
             PseudoClasses.Set(":hasnotime", true);
 
 
             var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
             var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
             if (timePattern.IndexOf("H") != -1)
             if (timePattern.IndexOf("H") != -1)
-                _clockIdentifier = "24HourClock";
+                SetCurrentValue(ClockIdentifierProperty, "24HourClock");
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -81,14 +73,16 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public int MinuteIncrement
         public int MinuteIncrement
         {
         {
-            get => _minuteIncrement;
-            set
-            {
-                if (value < 1 || value > 59)
-                    throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59");
-                SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value);
-                SetSelectedTimeText();
-            }
+            get => GetValue(MinuteIncrementProperty);
+            set => SetValue(MinuteIncrementProperty, value);
+        }
+
+        private static int CoerceMinuteIncrement(AvaloniaObject sender, int value)
+        {
+            if (value < 1 || value > 59)
+                throw new ArgumentOutOfRangeException(null, "1 >= MinuteIncrement <= 59");
+
+            return value;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -96,15 +90,17 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string ClockIdentifier
         public string ClockIdentifier
         {
         {
-            get => _clockIdentifier;
-            set
-            {
-                if (!(string.IsNullOrEmpty(value) || value == "12HourClock" || value == "24HourClock"))
-                    throw new ArgumentException("Invalid ClockIdentifier");
-                SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
-                SetGrid();
-                SetSelectedTimeText();
-            }
+
+            get => GetValue(ClockIdentifierProperty);
+            set => SetValue(ClockIdentifierProperty, value);
+        }
+
+        private static string CoerceClockIdentifier(AvaloniaObject sender, string value)
+        {
+            if (!(string.IsNullOrEmpty(value) || value == "12HourClock" || value == "24HourClock"))
+                throw new ArgumentException("Invalid ClockIdentifier", default(string));
+
+            return value;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -112,14 +108,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public TimeSpan? SelectedTime
         public TimeSpan? SelectedTime
         {
         {
-            get => _selectedTime;
-            set
-            {
-                var old = _selectedTime;
-                SetAndRaise(SelectedTimeProperty, ref _selectedTime, value);
-                OnSelectedTimeChanged(old, value);
-                SetSelectedTimeText();
-            }
+            get => GetValue(SelectedTimeProperty);
+            set => SetValue(SelectedTimeProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -173,6 +163,27 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            base.OnPropertyChanged(change);
+
+            if (change.Property == MinuteIncrementProperty)
+            {
+                SetSelectedTimeText();
+            }
+            else if (change.Property == ClockIdentifierProperty)
+            {
+                SetGrid();
+                SetSelectedTimeText();
+            }
+            else if (change.Property == SelectedTimeProperty)
+            {
+                var (oldValue, newValue) = change.GetOldAndNewValue<TimeSpan?>();
+                OnSelectedTimeChanged(oldValue, newValue);
+                SetSelectedTimeText();
+            }
+        }
+
         private void SetGrid()
         private void SetGrid()
         {
         {
             if (_contentGrid == null)
             if (_contentGrid == null)
@@ -270,7 +281,7 @@ namespace Avalonia.Controls
         private void OnConfirmed(object? sender, EventArgs e)
         private void OnConfirmed(object? sender, EventArgs e)
         {
         {
             _popup!.Close();
             _popup!.Close();
-            SelectedTime = _presenter!.Time;
+            SetCurrentValue(SelectedTimeProperty, _presenter!.Time);
         }
         }
     }
     }
 }
 }

+ 29 - 39
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@@ -30,28 +30,29 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="MinuteIncrement"/> property
         /// Defines the <see cref="MinuteIncrement"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<TimePickerPresenter, int> MinuteIncrementProperty =
-            TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(x => x.MinuteIncrement,
-                (x, v) => x.MinuteIncrement = v);
+        public static readonly StyledProperty<int> MinuteIncrementProperty =
+            TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="ClockIdentifier"/> property
         /// Defines the <see cref="ClockIdentifier"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<TimePickerPresenter, string> ClockIdentifierProperty =
-            TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(x => x.ClockIdentifier,
-                (x, v) => x.ClockIdentifier = v);
+        public static readonly StyledProperty<string> ClockIdentifierProperty =
+            TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Time"/> property
         /// Defines the <see cref="Time"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<TimePickerPresenter, TimeSpan> TimeProperty =
-            AvaloniaProperty.RegisterDirect<TimePickerPresenter, TimeSpan>(nameof(Time),
-                x => x.Time, (x, v) => x.Time = v);
+        public static readonly StyledProperty<TimeSpan> TimeProperty =
+            AvaloniaProperty.Register<TimePickerPresenter, TimeSpan>(nameof(Time));
 
 
         public TimePickerPresenter()
         public TimePickerPresenter()
         {
         {
-            Time = DateTime.Now.TimeOfDay;
-            KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
+            SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
+        }
+
+        static TimePickerPresenter()
+        {
+            KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue<TimePickerPresenter>(KeyboardNavigationMode.Cycle);
         }
         }
 
 
         // TemplateItems
         // TemplateItems
@@ -70,24 +71,13 @@ namespace Avalonia.Controls
         private Button? _minuteDownButton;
         private Button? _minuteDownButton;
         private Button? _periodDownButton;
         private Button? _periodDownButton;
 
 
-        // Backing Fields
-        private TimeSpan _time;
-        private int _minuteIncrement = 1;
-        private string _clockIdentifier = "12HourClock";
-
         /// <summary>
         /// <summary>
         /// Gets or sets the minute increment in the selector
         /// Gets or sets the minute increment in the selector
         /// </summary>
         /// </summary>
         public int MinuteIncrement
         public int MinuteIncrement
         {
         {
-            get => _minuteIncrement;
-            set
-            {
-                if (value < 1 || value > 59)
-                    throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59");
-                SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value);
-                InitPicker();
-            }
+            get => GetValue(MinuteIncrementProperty);
+            set => SetValue(MinuteIncrementProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -95,14 +85,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string ClockIdentifier
         public string ClockIdentifier
         {
         {
-            get => _clockIdentifier;
-            set
-            {
-                if (string.IsNullOrEmpty(value) || !(value == "12HourClock" || value == "24HourClock"))
-                    throw new ArgumentException("Invalid ClockIdentifier");
-                SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
-                InitPicker();
-            }
+            get => GetValue(ClockIdentifierProperty);
+            set => SetValue(ClockIdentifierProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -110,12 +94,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public TimeSpan Time
         public TimeSpan Time
         {
         {
-            get => _time;
-            set
-            {
-                SetAndRaise(TimeProperty, ref _time, value);
-                InitPicker();
-            }
+            get => GetValue(TimeProperty);
+            set => SetValue(TimeProperty, value);
         }
         }
 
 
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -162,6 +142,16 @@ namespace Avalonia.Controls
             InitPicker();
             InitPicker();
         }
         }
 
 
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            base.OnPropertyChanged(change);
+
+            if (change.Property == MinuteIncrementProperty || change.Property == ClockIdentifierProperty || change.Property == TimeProperty)
+            {
+                InitPicker();
+            }
+        }
+
         protected override void OnKeyDown(KeyEventArgs e)
         protected override void OnKeyDown(KeyEventArgs e)
         {
         {
             switch (e.Key)
             switch (e.Key)
@@ -197,7 +187,7 @@ namespace Avalonia.Controls
                 hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr;
                 hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr;
             }
             }
 
 
-            Time = new TimeSpan(hr, min, 0);
+            SetCurrentValue(TimeProperty, new TimeSpan(hr, min, 0));
 
 
             base.OnConfirmed();
             base.OnConfirmed();
         }
         }

+ 4 - 4
src/Avalonia.Controls/Documents/InlineCollection.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Controls.Documents
 
 
             this.ForEachItem(
             this.ForEachItem(
                 x =>
                 x =>
-                {                   
+                {
                     x.InlineHost = InlineHost;
                     x.InlineHost = InlineHost;
                     LogicalChildren?.Add(x);
                     LogicalChildren?.Add(x);
                     Invalidate();
                     Invalidate();
@@ -92,10 +92,10 @@ namespace Avalonia.Controls.Documents
         public override void Add(Inline inline)
         public override void Add(Inline inline)
         {
         {
             if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text))
             if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text))
-            {          
+            {
                 base.Add(new Run(textBlock._text));
                 base.Add(new Run(textBlock._text));
 
 
-                textBlock._text = null;                
+                textBlock._text = null;
             }
             }
 
 
             base.Add(inline);
             base.Add(inline);
@@ -159,7 +159,7 @@ namespace Avalonia.Controls.Documents
                         oldParent.Remove(child);
                         oldParent.Remove(child);
                     }
                     }
 
 
-                    if(newParent != null)
+                    if (newParent != null)
                     {
                     {
                         newParent.Add(child);
                         newParent.Add(child);
                     }
                     }

+ 9 - 14
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@@ -35,17 +35,14 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// <summary>
         /// Defines the <see cref="ShowMode"/> property
         /// Defines the <see cref="ShowMode"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<FlyoutBase, FlyoutShowMode> ShowModeProperty =
-            AvaloniaProperty.RegisterDirect<FlyoutBase, FlyoutShowMode>(nameof(ShowMode),
-                x => x.ShowMode, (x, v) => x.ShowMode = v);
+        public static readonly StyledProperty<FlyoutShowMode> ShowModeProperty =
+            AvaloniaProperty.Register<FlyoutBase, FlyoutShowMode>(nameof(ShowMode));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="OverlayInputPassThroughElement"/> property
         /// Defines the <see cref="OverlayInputPassThroughElement"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<FlyoutBase, IInputElement?> OverlayInputPassThroughElementProperty =
-            Popup.OverlayInputPassThroughElementProperty.AddOwner<FlyoutBase>(
-                o => o._overlayInputPassThroughElement,
-                (o, v) => o._overlayInputPassThroughElement = v);
+        public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty =
+            Popup.OverlayInputPassThroughElementProperty.AddOwner<FlyoutBase>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the AttachedFlyout property
         /// Defines the AttachedFlyout property
@@ -56,12 +53,10 @@ namespace Avalonia.Controls.Primitives
         private readonly Lazy<Popup> _popupLazy;
         private readonly Lazy<Popup> _popupLazy;
         private bool _isOpen;
         private bool _isOpen;
         private Control? _target;
         private Control? _target;
-        private FlyoutShowMode _showMode = FlyoutShowMode.Standard;
         private Rect? _enlargedPopupRect;
         private Rect? _enlargedPopupRect;
         private PixelRect? _enlargePopupRectScreenPixelRect;
         private PixelRect? _enlargePopupRectScreenPixelRect;
         private IDisposable? _transientDisposable;
         private IDisposable? _transientDisposable;
         private Action<IPopupHost?>? _popupHostChangedHandler;
         private Action<IPopupHost?>? _popupHostChangedHandler;
-        private IInputElement? _overlayInputPassThroughElement;
 
 
         static FlyoutBase()
         static FlyoutBase()
         {
         {
@@ -98,8 +93,8 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         public FlyoutShowMode ShowMode
         public FlyoutShowMode ShowMode
         {
         {
-            get => _showMode;
-            set => SetAndRaise(ShowModeProperty, ref _showMode, value);
+            get => GetValue(ShowModeProperty);
+            set => SetValue(ShowModeProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -117,8 +112,8 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         public IInputElement? OverlayInputPassThroughElement
         public IInputElement? OverlayInputPassThroughElement
         {
         {
-            get => _overlayInputPassThroughElement;
-            set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value);
+            get => GetValue(OverlayInputPassThroughElementProperty);
+            set => SetValue(OverlayInputPassThroughElementProperty, value);
         }
         }
 
 
         IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
         IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
@@ -244,7 +239,7 @@ namespace Avalonia.Controls.Primitives
             {
             {
                 Popup.PlacementTarget = Target = placementTarget;
                 Popup.PlacementTarget = Target = placementTarget;
                 ((ISetLogicalParent)Popup).SetParent(placementTarget);
                 ((ISetLogicalParent)Popup).SetParent(placementTarget);
-                Popup.SetValue(StyledElement.TemplatedParentProperty, placementTarget.TemplatedParent);
+                Popup.TemplatedParent = placementTarget.TemplatedParent;
             }
             }
 
 
             if (Popup.Child == null)
             if (Popup.Child == null)

+ 1 - 8
src/Avalonia.Controls/ItemsControl.cs

@@ -383,14 +383,7 @@ namespace Avalonia.Controls
             {
             {
                 hic.Header = item;
                 hic.Header = item;
                 hic.HeaderTemplate = itemTemplate;
                 hic.HeaderTemplate = itemTemplate;
-
-                itemTemplate ??= hic.FindDataTemplate(item) ?? this.FindDataTemplate(item);
-
-                if (itemTemplate is ITreeDataTemplate treeTemplate)
-                {
-                    if (item is not null && treeTemplate.ItemsSelector(item) is { } itemsBinding)
-                        BindingOperations.Apply(hic, ItemsProperty, itemsBinding, null);
-                }
+                hic.PrepareItemContainer();
             }
             }
         }
         }
 
 

+ 11 - 16
src/Avalonia.Controls/Label.cs

@@ -1,10 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Text;
-using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Templates;
-using Avalonia.Data;
+using Avalonia.Automation.Peers;
+using Avalonia.Controls.Automation.Peers;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
 
 
@@ -18,13 +13,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Target"/> Direct property
         /// Defines the <see cref="Target"/> Direct property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<Label, IInputElement?> TargetProperty =
-            AvaloniaProperty.RegisterDirect<Label, IInputElement?>(nameof(Target), lbl => lbl.Target, (lbl, inp) => lbl.Target = inp);
-
-        /// <summary>
-        /// Label focus target storage field
-        /// </summary>
-        private IInputElement? _target;
+        public static readonly StyledProperty<IInputElement?> TargetProperty =
+            AvaloniaProperty.Register<Label, IInputElement?>(nameof(Target));
 
 
         /// <summary>
         /// <summary>
         /// Label focus Target
         /// Label focus Target
@@ -32,8 +22,8 @@ namespace Avalonia.Controls
         [ResolveByName]
         [ResolveByName]
         public IInputElement? Target
         public IInputElement? Target
         {
         {
-            get => _target;
-            set => SetAndRaise(TargetProperty, ref _target, value);
+            get => GetValue(TargetProperty);
+            set => SetValue(TargetProperty, value);
         }
         }
 
 
         static Label()
         static Label()
@@ -71,5 +61,10 @@ namespace Avalonia.Controls
             }
             }
             base.OnPointerPressed(e);
             base.OnPointerPressed(e);
         }
         }
+
+        protected override AutomationPeer OnCreateAutomationPeer()
+        {
+            return new LabelAutomationPeer(this);
+        }
     }
     }
 }
 }

+ 9 - 13
src/Avalonia.Controls/MenuItem.cs

@@ -27,11 +27,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Command"/> property.
         /// Defines the <see cref="Command"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<MenuItem, ICommand?> CommandProperty =
-            Button.CommandProperty.AddOwner<MenuItem>(
-                menuItem => menuItem.Command,
-                (menuItem, command) => menuItem.Command = command,
-                enableDataValidation: true);
+        public static readonly StyledProperty<ICommand?> CommandProperty =
+            Button.CommandProperty.AddOwner<MenuItem>(new(enableDataValidation: true));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="HotKey"/> property.
         /// Defines the <see cref="HotKey"/> property.
@@ -113,7 +110,6 @@ namespace Avalonia.Controls
         private static readonly ITemplate<Panel> DefaultPanel =
         private static readonly ITemplate<Panel> DefaultPanel =
             new FuncTemplate<Panel>(() => new StackPanel());
             new FuncTemplate<Panel>(() => new StackPanel());
 
 
-        private ICommand? _command;
         private bool _commandCanExecute = true;
         private bool _commandCanExecute = true;
         private bool _commandBindingError;
         private bool _commandBindingError;
         private Popup? _popup;
         private Popup? _popup;
@@ -217,8 +213,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public ICommand? Command
         public ICommand? Command
         {
         {
-            get { return _command; }
-            set { SetAndRaise(CommandProperty, ref _command, value); }
+            get => GetValue(CommandProperty);
+            set => SetValue(CommandProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -337,7 +333,7 @@ namespace Avalonia.Controls
         /// <remarks>
         /// <remarks>
         /// This has the same effect as setting <see cref="IsSubMenuOpen"/> to true.
         /// This has the same effect as setting <see cref="IsSubMenuOpen"/> to true.
         /// </remarks>
         /// </remarks>
-        public void Open() => IsSubMenuOpen = true;
+        public void Open() => SetCurrentValue(IsSubMenuOpenProperty, true);
 
 
         /// <summary>
         /// <summary>
         /// Closes the submenu.
         /// Closes the submenu.
@@ -345,7 +341,7 @@ namespace Avalonia.Controls
         /// <remarks>
         /// <remarks>
         /// This has the same effect as setting <see cref="IsSubMenuOpen"/> to false.
         /// This has the same effect as setting <see cref="IsSubMenuOpen"/> to false.
         /// </remarks>
         /// </remarks>
-        public void Close() => IsSubMenuOpen = false;
+        public void Close() => SetCurrentValue(IsSubMenuOpenProperty, false);
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         void IMenuItem.RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent));
         void IMenuItem.RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent));
@@ -369,7 +365,7 @@ namespace Avalonia.Controls
         {
         {
             if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
             if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
             {
             {
-                HotKey = _hotkey;
+                SetCurrentValue(HotKeyProperty, _hotkey);
             }
             }
             
             
             base.OnAttachedToLogicalTree(e);
             base.OnAttachedToLogicalTree(e);
@@ -397,7 +393,7 @@ namespace Avalonia.Controls
             if (HotKey != null)
             if (HotKey != null)
             {
             {
                 _hotkey = HotKey;
                 _hotkey = HotKey;
-                HotKey = null;
+                SetCurrentValue(HotKeyProperty, null);
             }
             }
 
 
             base.OnDetachedFromLogicalTree(e);
             base.OnDetachedFromLogicalTree(e);
@@ -663,7 +659,7 @@ namespace Avalonia.Controls
                 }
                 }
 
 
                 RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent));
                 RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent));
-                IsSelected = true;
+                SetCurrentValue(IsSelectedProperty, true);
                 PseudoClasses.Add(":open");
                 PseudoClasses.Add(":open");
             }
             }
             else
             else

+ 2 - 2
src/Avalonia.Controls/NativeMenu.cs

@@ -79,12 +79,12 @@ namespace Avalonia.Controls
         }
         }
 
 
         public static readonly DirectProperty<NativeMenu, NativeMenuItem?> ParentProperty =
         public static readonly DirectProperty<NativeMenu, NativeMenuItem?> ParentProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem?>("Parent", o => o.Parent, (o, v) => o.Parent = v);
+            AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem?>(nameof(Parent), o => o.Parent);
 
 
         public NativeMenuItem? Parent
         public NativeMenuItem? Parent
         {
         {
             get => _parent;
             get => _parent;
-            set => SetAndRaise(ParentProperty, ref _parent, value);
+            internal set => SetAndRaise(ParentProperty, ref _parent, value);
         }
         }
 
 
         public void Add(NativeMenuItemBase item) => _items.Add(item);
         public void Add(NativeMenuItemBase item) => _items.Add(item);

+ 60 - 82
src/Avalonia.Controls/NativeMenuItem.cs

@@ -4,36 +4,13 @@ using Avalonia.Input;
 using Avalonia.Media.Imaging;
 using Avalonia.Media.Imaging;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
-using Avalonia.Reactive;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
 {
 {
     public class NativeMenuItem : NativeMenuItemBase, INativeMenuItemExporterEventsImplBridge
     public class NativeMenuItem : NativeMenuItemBase, INativeMenuItemExporterEventsImplBridge
     {
     {
-        private string? _header;
-        private KeyGesture? _gesture;
-        private bool _isEnabled = true;
-        private ICommand? _command;
-        private bool _isChecked = false;
-        private NativeMenuItemToggleType _toggleType;
-        private IBitmap? _icon;
         private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
         private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
 
 
-        private NativeMenu? _menu;
-
-        static NativeMenuItem()
-        {
-            MenuProperty.Changed.Subscribe(args =>
-            {
-                var item = (NativeMenuItem)args.Sender;
-                var value = args.NewValue.GetValueOrDefault()!;
-                if (value.Parent != null && value.Parent != item)
-                    throw new InvalidOperationException("NativeMenu already has a parent");
-                value.Parent = item;
-            });
-        }
-
-
         class CanExecuteChangedSubscriber : IWeakEventSubscriber<EventArgs>
         class CanExecuteChangedSubscriber : IWeakEventSubscriber<EventArgs>
         {
         {
             private readonly NativeMenuItem _parent;
             private readonly NativeMenuItem _parent;
@@ -60,78 +37,70 @@ namespace Avalonia.Controls
             Header = header;
             Header = header;
         }
         }
 
 
-        public static readonly DirectProperty<NativeMenuItem, NativeMenu?> MenuProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu?>(nameof(Menu), o => o.Menu, (o, v) => o.Menu = v);
+        public static readonly StyledProperty<NativeMenu?> MenuProperty =
+            AvaloniaProperty.Register<NativeMenuItem, NativeMenu?>(nameof(Menu), coerce: CoerceMenu);
 
 
         [Content]
         [Content]
         public NativeMenu? Menu
         public NativeMenu? Menu
         {
         {
-            get => _menu;
-            set
-            {
-                if (value != null && value.Parent != null && value.Parent != this)
-                    throw new InvalidOperationException("NativeMenu already has a parent");
-                SetAndRaise(MenuProperty, ref _menu, value);
-            }
+            get => GetValue(MenuProperty);
+            set => SetValue(MenuProperty, value);
         }
         }
 
 
-        public static readonly DirectProperty<NativeMenuItem, IBitmap?> IconProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenuItem, IBitmap?>(nameof(Icon), o => o.Icon, (o, v) => o.Icon = v);
+        private static NativeMenu? CoerceMenu(AvaloniaObject sender, NativeMenu? value)
+        {
+            if (value != null && value.Parent != null && value.Parent != sender)
+                throw new InvalidOperationException("NativeMenu already has a parent");
+            return value;
+        }
 
 
+        public static readonly StyledProperty<IBitmap?> IconProperty =
+            AvaloniaProperty.Register<NativeMenuItem, IBitmap?>(nameof(Icon));
 
 
         public IBitmap? Icon
         public IBitmap? Icon
         {
         {
-            get => _icon;
-            set => SetAndRaise(IconProperty, ref _icon, value);
+            get => GetValue(IconProperty);
+            set => SetValue(IconProperty, value);
         }  
         }  
 
 
-        public static readonly DirectProperty<NativeMenuItem, string?> HeaderProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenuItem, string?>(nameof(Header), o => o.Header, (o, v) => o.Header = v);
+        public static readonly StyledProperty<string?> HeaderProperty =
+            AvaloniaProperty.Register<NativeMenuItem, string?>(nameof(Header));
 
 
         public string? Header
         public string? Header
         {
         {
-            get => _header;
-            set => SetAndRaise(HeaderProperty, ref _header, value);
+            get => GetValue(HeaderProperty);
+            set => SetValue(HeaderProperty, value);
         }
         }
 
 
-        public static readonly DirectProperty<NativeMenuItem, KeyGesture?> GestureProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenuItem, KeyGesture?>(nameof(Gesture), o => o.Gesture, (o, v) => o.Gesture = v);
+        public static readonly StyledProperty<KeyGesture?> GestureProperty =
+            AvaloniaProperty.Register<NativeMenuItem, KeyGesture?>(nameof(Gesture));
 
 
         public KeyGesture? Gesture
         public KeyGesture? Gesture
         {
         {
-            get => _gesture;
-            set => SetAndRaise(GestureProperty, ref _gesture, value);
+            get => GetValue(GestureProperty);
+            set => SetValue(GestureProperty, value);
         }
         }
 
 
-        public static readonly DirectProperty<NativeMenuItem, bool> IsCheckedProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(
-                nameof(IsChecked),
-                o => o.IsChecked,
-                (o, v) => o.IsChecked = v);
+        public static readonly StyledProperty<bool> IsCheckedProperty =
+            AvaloniaProperty.Register<NativeMenuItem, bool>(nameof(IsChecked));
 
 
         public bool IsChecked
         public bool IsChecked
         {
         {
-            get => _isChecked;
-            set => SetAndRaise(IsCheckedProperty, ref _isChecked, value);
+            get => GetValue(IsCheckedProperty);
+            set => SetValue(IsCheckedProperty, value);
         }
         }
         
         
-        public static readonly DirectProperty<NativeMenuItem, NativeMenuItemToggleType> ToggleTypeProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenuItemToggleType>(
-                nameof(ToggleType),
-                o => o.ToggleType,
-                (o, v) => o.ToggleType = v);
+        public static readonly StyledProperty<NativeMenuItemToggleType> ToggleTypeProperty =
+            AvaloniaProperty.Register<NativeMenuItem, NativeMenuItemToggleType>(nameof(ToggleType));
 
 
         public NativeMenuItemToggleType ToggleType
         public NativeMenuItemToggleType ToggleType
         {
         {
-            get => _toggleType;
-            set => SetAndRaise(ToggleTypeProperty, ref _toggleType, value);
+            get => GetValue(ToggleTypeProperty);
+            set => SetValue(ToggleTypeProperty, value);
         }
         }
 
 
-        public static readonly DirectProperty<NativeMenuItem, ICommand?> CommandProperty =
-            Button.CommandProperty.AddOwner<NativeMenuItem>(
-                menuItem => menuItem.Command,
-                (menuItem, command) => menuItem.Command = command,
-                enableDataValidation: true);
+        public static readonly StyledProperty<ICommand?> CommandProperty =
+            Button.CommandProperty.AddOwner<NativeMenuItem>(new(enableDataValidation: true));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="CommandParameter"/> property.
         /// Defines the <see cref="CommandParameter"/> property.
@@ -139,37 +108,26 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<object?> CommandParameterProperty =
         public static readonly StyledProperty<object?> CommandParameterProperty =
             Button.CommandParameterProperty.AddOwner<NativeMenuItem>();
             Button.CommandParameterProperty.AddOwner<NativeMenuItem>();
 
 
-        public static readonly DirectProperty<NativeMenuItem, bool> IsEnabledProperty =
-           AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(IsEnabled), o => o.IsEnabled, (o, v) => o.IsEnabled = v, true);
+        public static readonly StyledProperty<bool> IsEnabledProperty =
+           AvaloniaProperty.Register<NativeMenuItem, bool>(nameof(IsEnabled), true);
 
 
         public bool IsEnabled
         public bool IsEnabled
         {
         {
-            get => _isEnabled;
-            set => SetAndRaise(IsEnabledProperty, ref _isEnabled, value);
+            get => GetValue(IsEnabledProperty);
+            set => SetValue(IsEnabledProperty, value);
         }
         }
 
 
         void CanExecuteChanged()
         void CanExecuteChanged()
         {
         {
-            IsEnabled = _command?.CanExecute(CommandParameter) ?? true;
+            SetCurrentValue(IsEnabledProperty, Command?.CanExecute(CommandParameter) ?? true);
         }
         }
 
 
         public bool HasClickHandlers => Click != null;
         public bool HasClickHandlers => Click != null;
 
 
         public ICommand? Command
         public ICommand? Command
         {
         {
-            get => _command;
-            set
-            {
-                if (_command != null)
-                    WeakEvents.CommandCanExecuteChanged.Unsubscribe(_command, _canExecuteChangedSubscriber);
-
-                SetAndRaise(CommandProperty, ref _command, value);
-
-                if (_command != null)
-                    WeakEvents.CommandCanExecuteChanged.Subscribe(_command, _canExecuteChangedSubscriber);
-
-                CanExecuteChanged();
-            }
+            get => GetValue(CommandProperty);
+            set => SetValue(CommandProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -196,8 +154,28 @@ namespace Avalonia.Controls
                 Command.Execute(CommandParameter);
                 Command.Execute(CommandParameter);
             }
             }
         }
         }
+
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            base.OnPropertyChanged(change);
+
+            if (change.Property == MenuProperty && change.NewValue is NativeMenu newMenu)
+            {
+                if (newMenu.Parent != null && newMenu.Parent != this)
+                    throw new InvalidOperationException("NativeMenu already has a parent");
+                newMenu.Parent = this;
+            }
+            else if (change.Property == CommandProperty)
+            {
+                if (change.OldValue is ICommand oldCommand)
+                    WeakEvents.CommandCanExecuteChanged.Unsubscribe(oldCommand, _canExecuteChangedSubscriber);
+                if (change.NewValue is ICommand newCommand)
+                    WeakEvents.CommandCanExecuteChanged.Subscribe(newCommand, _canExecuteChangedSubscriber);
+                CanExecuteChanged();
+            }
+        }
     }
     }
-    
+
     public enum NativeMenuItemToggleType
     public enum NativeMenuItemToggleType
     {
     {
         None,
         None,

+ 2 - 2
src/Avalonia.Controls/NativeMenuItemBase.cs

@@ -12,12 +12,12 @@ namespace Avalonia.Controls
         }
         }
 
 
         public static readonly DirectProperty<NativeMenuItemBase, NativeMenu?> ParentProperty =
         public static readonly DirectProperty<NativeMenuItemBase, NativeMenu?> ParentProperty =
-            AvaloniaProperty.RegisterDirect<NativeMenuItemBase, NativeMenu?>("Parent", o => o.Parent, (o, v) => o.Parent = v);
+            AvaloniaProperty.RegisterDirect<NativeMenuItemBase, NativeMenu?>(nameof(Parent), o => o.Parent);
 
 
         public NativeMenu? Parent
         public NativeMenu? Parent
         {
         {
             get => _parent;
             get => _parent;
-            set => SetAndRaise(ParentProperty, ref _parent, value);
+            internal set => SetAndRaise(ParentProperty, ref _parent, value);
         }
         }
     }
     }
 }
 }

+ 4 - 5
src/Avalonia.Controls/Notifications/NotificationCard.cs

@@ -13,7 +13,6 @@ namespace Avalonia.Controls.Notifications
     [PseudoClasses(":error", ":information", ":success", ":warning")]
     [PseudoClasses(":error", ":information", ":success", ":warning")]
     public class NotificationCard : ContentControl
     public class NotificationCard : ContentControl
     {
     {
-        private bool _isClosed;
         private bool _isClosing;
         private bool _isClosing;
 
 
         static NotificationCard()
         static NotificationCard()
@@ -84,15 +83,15 @@ namespace Avalonia.Controls.Notifications
         /// </summary>
         /// </summary>
         public bool IsClosed
         public bool IsClosed
         {
         {
-            get { return _isClosed; }
-            set { SetAndRaise(IsClosedProperty, ref _isClosed, value); }
+            get => GetValue(IsClosedProperty);
+            set => SetValue(IsClosedProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsClosed"/> property.
         /// Defines the <see cref="IsClosed"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<NotificationCard, bool> IsClosedProperty =
-            AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v);
+        public static readonly StyledProperty<bool> IsClosedProperty =
+            AvaloniaProperty.Register<NotificationCard, bool>(nameof(IsClosed));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="NotificationClosed"/> event.
         /// Defines the <see cref="NotificationClosed"/> event.

+ 38 - 52
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@@ -43,16 +43,14 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="ClipValueToMinMax"/> property.
         /// Defines the <see cref="ClipValueToMinMax"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<NumericUpDown, bool> ClipValueToMinMaxProperty =
-            AvaloniaProperty.RegisterDirect<NumericUpDown, bool>(nameof(ClipValueToMinMax),
-                updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
+        public static readonly StyledProperty<bool> ClipValueToMinMaxProperty =
+            AvaloniaProperty.Register<NumericUpDown, bool>(nameof(ClipValueToMinMax));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="NumberFormat"/> property.
         /// Defines the <see cref="NumberFormat"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<NumericUpDown, NumberFormatInfo?> NumberFormatProperty =
-            AvaloniaProperty.RegisterDirect<NumericUpDown, NumberFormatInfo?>(nameof(NumberFormat), o => o.NumberFormat,
-                (o, v) => o.NumberFormat = v, NumberFormatInfo.CurrentInfo);
+        public static readonly StyledProperty<NumberFormatInfo?> NumberFormatProperty =
+            AvaloniaProperty.Register<NumericUpDown, NumberFormatInfo?>(nameof(NumberFormat), NumberFormatInfo.CurrentInfo);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="FormatString"/> property.
         /// Defines the <see cref="FormatString"/> property.
@@ -87,30 +85,28 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="ParsingNumberStyle"/> property.
         /// Defines the <see cref="ParsingNumberStyle"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<NumericUpDown, NumberStyles> ParsingNumberStyleProperty =
-            AvaloniaProperty.RegisterDirect<NumericUpDown, NumberStyles>(nameof(ParsingNumberStyle),
-                updown => updown.ParsingNumberStyle, (updown, style) => updown.ParsingNumberStyle = style);
+        public static readonly StyledProperty<NumberStyles> ParsingNumberStyleProperty =
+            AvaloniaProperty.Register<NumericUpDown, NumberStyles>(nameof(ParsingNumberStyle), NumberStyles.Any);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Text"/> property.
         /// Defines the <see cref="Text"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<NumericUpDown, string?> TextProperty =
-            AvaloniaProperty.RegisterDirect<NumericUpDown, string?>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
+        public static readonly StyledProperty<string?> TextProperty =
+            AvaloniaProperty.Register<NumericUpDown, string?>(nameof(Text),
                 defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
                 defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="TextConverter"/> property.
         /// Defines the <see cref="TextConverter"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<NumericUpDown, IValueConverter?> TextConverterProperty =
-            AvaloniaProperty.RegisterDirect<NumericUpDown, IValueConverter?>(nameof(TextConverter),
-                updown => updown.TextConverter, (o, v) => o.TextConverter = v, null, BindingMode.OneWay, false);
+        public static readonly StyledProperty<IValueConverter?> TextConverterProperty =
+            AvaloniaProperty.Register<NumericUpDown, IValueConverter?>(nameof(TextConverter), defaultBindingMode: BindingMode.OneWay);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Value"/> property.
         /// Defines the <see cref="Value"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<NumericUpDown, decimal?> ValueProperty =
-            AvaloniaProperty.RegisterDirect<NumericUpDown, decimal?>(nameof(Value), updown => updown.Value,
-                (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
+        public static readonly StyledProperty<decimal?> ValueProperty =
+            AvaloniaProperty.Register<NumericUpDown, decimal?>(nameof(Value), coerce: (s,v) => ((NumericUpDown)s).OnCoerceValue(v),
+                defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Watermark"/> property.
         /// Defines the <see cref="Watermark"/> property.
@@ -132,15 +128,9 @@ namespace Avalonia.Controls
 
 
         private IDisposable? _textBoxTextChangedSubscription;
         private IDisposable? _textBoxTextChangedSubscription;
 
 
-        private decimal? _value;
-        private string? _text;
-        private IValueConverter? _textConverter;
         private bool _internalValueSet;
         private bool _internalValueSet;
-        private bool _clipValueToMinMax;
         private bool _isSyncingTextAndValueProperties;
         private bool _isSyncingTextAndValueProperties;
         private bool _isTextChangedFromUI;
         private bool _isTextChangedFromUI;
-        private NumberStyles _parsingNumberStyle = NumberStyles.Any;
-        private NumberFormatInfo? _numberFormat;
 
 
         /// <summary>
         /// <summary>
         /// Gets the Spinner template part.
         /// Gets the Spinner template part.
@@ -184,8 +174,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public bool ClipValueToMinMax
         public bool ClipValueToMinMax
         {
         {
-            get { return _clipValueToMinMax; }
-            set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
+            get => GetValue(ClipValueToMinMaxProperty);
+            set => SetValue(ClipValueToMinMaxProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -193,8 +183,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public NumberFormatInfo? NumberFormat
         public NumberFormatInfo? NumberFormat
         {
         {
-            get { return _numberFormat; }
-            set { SetAndRaise(NumberFormatProperty, ref _numberFormat, value); }
+            get => GetValue(NumberFormatProperty);
+            set => SetValue(NumberFormatProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -249,8 +239,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public NumberStyles ParsingNumberStyle
         public NumberStyles ParsingNumberStyle
         {
         {
-            get { return _parsingNumberStyle; }
-            set { SetAndRaise(ParsingNumberStyleProperty, ref _parsingNumberStyle, value); }
+            get => GetValue(ParsingNumberStyleProperty);
+            set => SetValue(ParsingNumberStyleProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -258,8 +248,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public string? Text
         public string? Text
         {
         {
-            get { return _text; }
-            set { SetAndRaise(TextProperty, ref _text, value); }
+            get => GetValue(TextProperty);
+            set => SetValue(TextProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -269,8 +259,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public IValueConverter? TextConverter
         public IValueConverter? TextConverter
         {
         {
-            get { return _textConverter; }
-            set { SetAndRaise(TextConverterProperty, ref _textConverter, value); }
+            get => GetValue(TextConverterProperty);
+            set => SetValue(TextConverterProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -278,12 +268,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public decimal? Value
         public decimal? Value
         {
         {
-            get { return _value; }
-            set
-            {
-                value = OnCoerceValue(value);
-                SetAndRaise(ValueProperty, ref _value, value);
-            }
+            get => GetValue(ValueProperty);
+            set => SetValue(ValueProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -475,7 +461,7 @@ namespace Avalonia.Controls
             }
             }
             if (ClipValueToMinMax && Value.HasValue)
             if (ClipValueToMinMax && Value.HasValue)
             {
             {
-                Value = MathUtilities.Clamp(Value.Value, Minimum, Maximum);
+                SetCurrentValue(ValueProperty, MathUtilities.Clamp(Value.Value, Minimum, Maximum));
             }
             }
         }
         }
 
 
@@ -492,7 +478,7 @@ namespace Avalonia.Controls
             }
             }
             if (ClipValueToMinMax && Value.HasValue)
             if (ClipValueToMinMax && Value.HasValue)
             {
             {
-                Value = MathUtilities.Clamp(Value.Value, Minimum, Maximum);
+                SetCurrentValue(ValueProperty, MathUtilities.Clamp(Value.Value, Minimum, Maximum));
             }
             }
         }
         }
 
 
@@ -508,7 +494,7 @@ namespace Avalonia.Controls
                 SyncTextAndValueProperties(true, Text);
                 SyncTextAndValueProperties(true, Text);
             }
             }
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Called when the <see cref="Text"/> property value changed.
         /// Called when the <see cref="Text"/> property value changed.
         /// </summary>
         /// </summary>
@@ -675,8 +661,8 @@ namespace Avalonia.Controls
             {
             {
                 result = Minimum;
                 result = Minimum;
             }
             }
-            
-            Value = MathUtilities.Clamp(result, Minimum, Maximum);
+
+            SetCurrentValue(ValueProperty, MathUtilities.Clamp(result, Minimum, Maximum));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -685,7 +671,7 @@ namespace Avalonia.Controls
         private void OnDecrement()
         private void OnDecrement()
         {
         {
             decimal result;
             decimal result;
-            
+
             if (Value.HasValue)
             if (Value.HasValue)
             {
             {
                 result = Value.Value - Increment;
                 result = Value.Value - Increment;
@@ -694,8 +680,8 @@ namespace Avalonia.Controls
             {
             {
                 result = Maximum;
                 result = Maximum;
             }
             }
-            
-            Value = MathUtilities.Clamp(result, Minimum, Maximum);
+
+            SetCurrentValue(ValueProperty, MathUtilities.Clamp(result, Minimum, Maximum));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -712,7 +698,7 @@ namespace Avalonia.Controls
                 {
                 {
                     validDirections = ValidSpinDirections.Increase | ValidSpinDirections.Decrease;
                     validDirections = ValidSpinDirections.Increase | ValidSpinDirections.Decrease;
                 }
                 }
-                
+
                 if (Value < Maximum)
                 if (Value < Maximum)
                 {
                 {
                     validDirections = validDirections | ValidSpinDirections.Increase;
                     validDirections = validDirections | ValidSpinDirections.Increase;
@@ -862,7 +848,7 @@ namespace Avalonia.Controls
             _internalValueSet = true;
             _internalValueSet = true;
             try
             try
             {
             {
-                Value = value;
+                SetCurrentValue(ValueProperty, value);
             }
             }
             finally
             finally
             {
             {
@@ -907,7 +893,7 @@ namespace Avalonia.Controls
                 _isTextChangedFromUI = true;
                 _isTextChangedFromUI = true;
                 if (TextBox != null)
                 if (TextBox != null)
                 {
                 {
-                    Text = TextBox.Text;
+                    SetCurrentValue(TextProperty, TextBox.Text);
                 }
                 }
             }
             }
             finally
             finally
@@ -1026,7 +1012,7 @@ namespace Avalonia.Controls
                         var newText = ConvertValueToText();
                         var newText = ConvertValueToText();
                         if (!Equals(Text, newText))
                         if (!Equals(Text, newText))
                         {
                         {
-                            Text = newText;
+                            SetCurrentValue(TextProperty, newText);
                         }
                         }
                     }
                     }
 
 
@@ -1066,7 +1052,7 @@ namespace Avalonia.Controls
             {
             {
                 return null;
                 return null;
             }
             }
-            
+
             if (TextConverter != null)
             if (TextConverter != null)
             {
             {
                 var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture);
                 var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture);

+ 24 - 11
src/Avalonia.Controls/Panel.cs

@@ -1,13 +1,12 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Linq;
 using System.Linq;
+using Avalonia.Controls.Presenters;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
 using Avalonia.Reactive;
 using Avalonia.Reactive;
-using Avalonia.Styling;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
 {
 {
@@ -59,6 +58,11 @@ namespace Avalonia.Controls
             set { SetValue(BackgroundProperty, value); }
             set { SetValue(BackgroundProperty, value); }
         }
         }
 
 
+        /// <summary>
+        /// Gets whether the <see cref="Panel"/> hosts the items created by an <see cref="ItemsPresenter"/>.
+        /// </summary>
+        public bool IsItemsHost { get; internal set; }
+
         event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
         event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
         {
         {
             add
             add
@@ -129,24 +133,29 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         /// <param name="e">The event args.</param>
         protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
         protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
         {
-            List<Control> controls;
-
             switch (e.Action)
             switch (e.Action)
             {
             {
                 case NotifyCollectionChangedAction.Add:
                 case NotifyCollectionChangedAction.Add:
-                    controls = e.NewItems!.OfType<Control>().ToList();
-                    LogicalChildren.InsertRange(e.NewStartingIndex, controls);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Control>().ToList());
+                    }
                     VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Visual>());
                     VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Visual>());
                     break;
                     break;
 
 
                 case NotifyCollectionChangedAction.Move:
                 case NotifyCollectionChangedAction.Move:
-                    LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
-                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
+                    }
+                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
                     break;
                     break;
 
 
                 case NotifyCollectionChangedAction.Remove:
                 case NotifyCollectionChangedAction.Remove:
-                    controls = e.OldItems!.OfType<Control>().ToList();
-                    LogicalChildren.RemoveAll(controls);
+                    if (!IsItemsHost)
+                    {
+                        LogicalChildren.RemoveAll(e.OldItems!.OfType<Control>().ToList());
+                    }
                     VisualChildren.RemoveAll(e.OldItems!.OfType<Visual>());
                     VisualChildren.RemoveAll(e.OldItems!.OfType<Visual>());
                     break;
                     break;
 
 
@@ -155,7 +164,10 @@ namespace Avalonia.Controls
                     {
                     {
                         var index = i + e.OldStartingIndex;
                         var index = i + e.OldStartingIndex;
                         var child = (Control)e.NewItems![i]!;
                         var child = (Control)e.NewItems![i]!;
-                        LogicalChildren[index] = child;
+                        if (!IsItemsHost)
+                        {
+                            LogicalChildren[index] = child;
+                        }
                         VisualChildren[index] = child;
                         VisualChildren[index] = child;
                     }
                     }
                     break;
                     break;
@@ -200,6 +212,7 @@ namespace Avalonia.Controls
             return child is Control control ? Children.IndexOf(control) : -1;
             return child is Control control ? Children.IndexOf(control) : -1;
         }
         }
 
 
+        /// <inheritdoc />
         public bool TryGetTotalCount(out int count)
         public bool TryGetTotalCount(out int count)
         {
         {
             count = Children.Count;
             count = Children.Count;

+ 55 - 0
src/Avalonia.Controls/Platform/IInsetsManager.cs

@@ -0,0 +1,55 @@
+using System;
+using Avalonia.Metadata;
+
+#nullable enable
+namespace Avalonia.Controls.Platform
+{
+    [Unstable]
+    [NotClientImplementable]
+    public interface IInsetsManager
+    {
+        /// <summary>
+        /// Gets or sets whether the system bars are visible.
+        /// </summary>
+        bool? IsSystemBarVisible { get; set; }
+
+        /// <summary>
+        /// Gets or sets whether the window draws edge to edge. behind any visibile system bars.
+        /// </summary>
+        bool DisplayEdgeToEdge { get; set; }
+
+        /// <summary>
+        /// Gets the current safe area padding.
+        /// </summary>
+        Thickness SafeAreaPadding { get; }
+        
+        /// <summary>
+        /// Occurs when safe area for the current window changes.
+        /// </summary>
+        event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
+    }
+    
+    public class SafeAreaChangedArgs : EventArgs
+    {
+        public SafeAreaChangedArgs(Thickness safeArePadding)
+        {
+            SafeAreaPadding = safeArePadding;
+        }
+
+        /// <inheritdoc cref="IInsetsManager.GetSafeAreaPadding"/>
+        public Thickness SafeAreaPadding { get; }
+    }
+
+    public enum SystemBarTheme
+    {
+        /// <summary>
+        /// Light system bar theme, with light background and a dark foreground
+        /// </summary>
+        Light,
+
+        /// <summary>
+        /// Bark system bar theme, with dark background and a light foreground
+        /// </summary>
+        Dark
+    }
+}

+ 4 - 7
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -156,16 +156,13 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// <summary>
         /// Defines the <see cref="RecognizesAccessKey"/> property
         /// Defines the <see cref="RecognizesAccessKey"/> property
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<ContentPresenter, bool> RecognizesAccessKeyProperty =
-            AvaloniaProperty.RegisterDirect<ContentPresenter, bool>(
-                nameof(RecognizesAccessKey),
-                cp => cp.RecognizesAccessKey, (cp, value) => cp.RecognizesAccessKey = value);
+        public static readonly StyledProperty<bool> RecognizesAccessKeyProperty =
+            AvaloniaProperty.Register<ContentPresenter, bool>(nameof(RecognizesAccessKey));
 
 
         private Control? _child;
         private Control? _child;
         private bool _createdChild;
         private bool _createdChild;
         private IRecyclingDataTemplate? _recyclingDataTemplate;
         private IRecyclingDataTemplate? _recyclingDataTemplate;
         private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
         private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
-        private bool _recognizesAccessKey;
 
 
         /// <summary>
         /// <summary>
         /// Initializes static members of the <see cref="ContentPresenter"/> class.
         /// Initializes static members of the <see cref="ContentPresenter"/> class.
@@ -386,8 +383,8 @@ namespace Avalonia.Controls.Presenters
         /// </summary>
         /// </summary>
         public bool RecognizesAccessKey
         public bool RecognizesAccessKey
         {
         {
-            get => _recognizesAccessKey;
-            set => SetAndRaise(RecognizesAccessKeyProperty, ref _recognizesAccessKey, value);
+            get => GetValue(RecognizesAccessKeyProperty);
+            set => SetValue(RecognizesAccessKeyProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 1
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -166,7 +166,8 @@ namespace Avalonia.Controls.Presenters
                 }
                 }
 
 
                 Panel = ItemsPanel.Build();
                 Panel = ItemsPanel.Build();
-                Panel.SetValue(TemplatedParentProperty, TemplatedParent);
+                Panel.TemplatedParent = TemplatedParent;
+                Panel.IsItemsHost = true;
                 _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
                 _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
                 LogicalChildren.Add(Panel);
                 LogicalChildren.Add(Panel);
                 VisualChildren.Add(Panel);
                 VisualChildren.Add(Panel);

+ 47 - 0
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@@ -1,6 +1,8 @@
+using System;
 using Avalonia.Collections;
 using Avalonia.Collections;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Templates;
+using Avalonia.Data;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
 
 
 namespace Avalonia.Controls.Primitives
 namespace Avalonia.Controls.Primitives
@@ -10,6 +12,9 @@ namespace Avalonia.Controls.Primitives
     /// </summary>
     /// </summary>
     public class HeaderedItemsControl : ItemsControl, IContentPresenterHost
     public class HeaderedItemsControl : ItemsControl, IContentPresenterHost
     {
     {
+        private IDisposable? _itemsBinding;
+        private bool _prepareItemContainerOnAttach;
+
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Header"/> property.
         /// Defines the <see cref="Header"/> property.
         /// </summary>
         /// </summary>
@@ -60,6 +65,17 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         /// <inheritdoc/>
         IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
         IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
 
 
+        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToLogicalTree(e);
+
+            if (_prepareItemContainerOnAttach)
+            {
+                PrepareItemContainer();
+                _prepareItemContainerOnAttach = false;
+            }
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         {
         {
@@ -81,6 +97,37 @@ namespace Avalonia.Controls.Primitives
             return false;
             return false;
         }
         }
 
 
+        internal void PrepareItemContainer()
+        {
+            _itemsBinding?.Dispose();
+            _itemsBinding = null;
+
+            var item = Header;
+
+            if (item is null)
+            {
+                _prepareItemContainerOnAttach = false;
+                return;
+            }
+
+            var headerTemplate = HeaderTemplate;
+
+            if (headerTemplate is null)
+            {
+                if (((ILogical)this).IsAttachedToLogicalTree)
+                    headerTemplate = this.FindDataTemplate(item);
+                else
+                    _prepareItemContainerOnAttach = true;
+            }
+
+            if (headerTemplate is ITreeDataTemplate treeTemplate &&
+                treeTemplate.Match(item) &&
+                treeTemplate.ItemsSelector(item) is { } itemsBinding)
+            {
+                _itemsBinding = BindingOperations.Apply(this, ItemsProperty, itemsBinding, null);
+            }
+        }
+
         private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
         private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
         {
         {
             if (e.OldValue is ILogical oldChild)
             if (e.OldValue is ILogical oldChild)

+ 11 - 20
src/Avalonia.Controls/Primitives/Popup.cs

@@ -2,7 +2,6 @@ using System;
 using System.ComponentModel;
 using System.ComponentModel;
 using Avalonia.Reactive;
 using Avalonia.Reactive;
 using Avalonia.Automation.Peers;
 using Avalonia.Automation.Peers;
-using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Diagnostics;
 using Avalonia.Controls.Diagnostics;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Controls.Primitives.PopupPositioning;
@@ -41,11 +40,8 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsOpen"/> property.
         /// Defines the <see cref="IsOpen"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<Popup, bool> IsOpenProperty =
-            AvaloniaProperty.RegisterDirect<Popup, bool>(
-                nameof(IsOpen),
-                o => o.IsOpen,
-                (o, v) => o.IsOpen = v);
+        public static readonly StyledProperty<bool> IsOpenProperty =
+            AvaloniaProperty.Register<Popup, bool>(nameof(IsOpen));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="PlacementAnchor"/> property.
         /// Defines the <see cref="PlacementAnchor"/> property.
@@ -90,11 +86,8 @@ namespace Avalonia.Controls.Primitives
         public static readonly StyledProperty<bool> OverlayDismissEventPassThroughProperty =
         public static readonly StyledProperty<bool> OverlayDismissEventPassThroughProperty =
             AvaloniaProperty.Register<Popup, bool>(nameof(OverlayDismissEventPassThrough));
             AvaloniaProperty.Register<Popup, bool>(nameof(OverlayDismissEventPassThrough));
 
 
-        public static readonly DirectProperty<Popup, IInputElement?> OverlayInputPassThroughElementProperty =
-            AvaloniaProperty.RegisterDirect<Popup, IInputElement?>(
-                nameof(OverlayInputPassThroughElement),
-                o => o.OverlayInputPassThroughElement,
-                (o, v) => o.OverlayInputPassThroughElement = v);
+        public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty =
+            AvaloniaProperty.Register<Popup, IInputElement?>(nameof(OverlayInputPassThroughElement));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="HorizontalOffset"/> property.
         /// Defines the <see cref="HorizontalOffset"/> property.
@@ -121,10 +114,8 @@ namespace Avalonia.Controls.Primitives
             AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
             AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
 
 
         private bool _isOpenRequested;
         private bool _isOpenRequested;
-        private bool _isOpen;
         private bool _ignoreIsOpenChanged;
         private bool _ignoreIsOpenChanged;
         private PopupOpenState? _openState;
         private PopupOpenState? _openState;
-        private IInputElement? _overlayInputPassThroughElement;
         private Action<IPopupHost?>? _popupHostChangedHandler;
         private Action<IPopupHost?>? _popupHostChangedHandler;
 
 
         /// <summary>
         /// <summary>
@@ -209,8 +200,8 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         public bool IsOpen
         public bool IsOpen
         {
         {
-            get { return _isOpen; }
-            set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
+            get => GetValue(IsOpenProperty);
+            set => SetValue(IsOpenProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -301,8 +292,8 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         public IInputElement? OverlayInputPassThroughElement
         public IInputElement? OverlayInputPassThroughElement
         {
         {
-            get => _overlayInputPassThroughElement;
-            set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value);
+            get => GetValue(OverlayInputPassThroughElementProperty);
+            set => SetValue(OverlayInputPassThroughElementProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -486,7 +477,7 @@ namespace Avalonia.Controls.Primitives
 
 
             using (BeginIgnoringIsOpen())
             using (BeginIgnoringIsOpen())
             {
             {
-                IsOpen = true;
+                SetCurrentValue(IsOpenProperty, true);
             }
             }
 
 
             Opened?.Invoke(this, EventArgs.Empty);
             Opened?.Invoke(this, EventArgs.Empty);
@@ -704,7 +695,7 @@ namespace Avalonia.Controls.Primitives
             {
             {
                 using (BeginIgnoringIsOpen())
                 using (BeginIgnoringIsOpen())
                 {
                 {
-                    IsOpen = false;
+                    SetCurrentValue(IsOpenProperty, false);
                 }
                 }
 
 
                 return;
                 return;
@@ -717,7 +708,7 @@ namespace Avalonia.Controls.Primitives
 
 
             using (BeginIgnoringIsOpen())
             using (BeginIgnoringIsOpen())
             {
             {
-                IsOpen = false;
+                SetCurrentValue(IsOpenProperty, false);
             }
             }
 
 
             Closed?.Invoke(this, EventArgs.Empty);
             Closed?.Invoke(this, EventArgs.Empty);

+ 2 - 2
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -275,7 +275,7 @@ namespace Avalonia.Controls.Primitives
                 {
                 {
                     foreach (var child in this.GetTemplateChildren())
                     foreach (var child in this.GetTemplateChildren())
                     {
                     {
-                        child.SetValue(TemplatedParentProperty, null);
+                        child.TemplatedParent = null;
                         ((ISetLogicalParent)child).SetParent(null);
                         ((ISetLogicalParent)child).SetParent(null);
                     }
                     }
 
 
@@ -377,7 +377,7 @@ namespace Avalonia.Controls.Primitives
         /// <param name="templatedParent">The templated parent to apply.</param>
         /// <param name="templatedParent">The templated parent to apply.</param>
         internal static void ApplyTemplatedParent(StyledElement control, AvaloniaObject? templatedParent)
         internal static void ApplyTemplatedParent(StyledElement control, AvaloniaObject? templatedParent)
         {
         {
-            control.SetValue(TemplatedParentProperty, templatedParent);
+            control.TemplatedParent = templatedParent;
 
 
             var children = control.LogicalChildren;
             var children = control.LogicalChildren;
             var count = children.Count;
             var count = children.Count;

+ 13 - 18
src/Avalonia.Controls/Primitives/ToggleButton.cs

@@ -15,12 +15,8 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsChecked"/> property.
         /// Defines the <see cref="IsChecked"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty =
-            AvaloniaProperty.RegisterDirect<ToggleButton, bool?>(
-                nameof(IsChecked),
-                o => o.IsChecked,
-                (o, v) => o.IsChecked = v,
-                unsetValue: false,
+        public static readonly StyledProperty<bool?> IsCheckedProperty =
+            AvaloniaProperty.Register<ToggleButton, bool?>(nameof(IsChecked), false,
                 defaultBindingMode: BindingMode.TwoWay);
                 defaultBindingMode: BindingMode.TwoWay);
 
 
         /// <summary>
         /// <summary>
@@ -64,8 +60,6 @@ namespace Avalonia.Controls.Primitives
                 nameof(IsCheckedChanged),
                 nameof(IsCheckedChanged),
                 RoutingStrategies.Bubble);
                 RoutingStrategies.Bubble);
 
 
-        private bool? _isChecked = false;
-
         static ToggleButton()
         static ToggleButton()
         {
         {
         }
         }
@@ -119,12 +113,8 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         public bool? IsChecked
         public bool? IsChecked
         {
         {
-            get => _isChecked;
-            set 
-            { 
-                SetAndRaise(IsCheckedProperty, ref _isChecked, value);
-                UpdatePseudoClasses(IsChecked);
-            }
+            get => GetValue(IsCheckedProperty);
+            set => SetValue(IsCheckedProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -147,28 +137,31 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         protected virtual void Toggle()
         protected virtual void Toggle()
         {
         {
+            bool? newValue;
             if (IsChecked.HasValue)
             if (IsChecked.HasValue)
             {
             {
                 if (IsChecked.Value)
                 if (IsChecked.Value)
                 {
                 {
                     if (IsThreeState)
                     if (IsThreeState)
                     {
                     {
-                        IsChecked = null;
+                        newValue = null;
                     }
                     }
                     else
                     else
                     {
                     {
-                        IsChecked = false;
+                        newValue = false;
                     }
                     }
                 }
                 }
                 else
                 else
                 {
                 {
-                    IsChecked = true;
+                    newValue = true;
                 }
                 }
             }
             }
             else
             else
             {
             {
-                IsChecked = false;
+                newValue = false;
             }
             }
+
+            SetCurrentValue(IsCheckedProperty, newValue);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -224,6 +217,8 @@ namespace Avalonia.Controls.Primitives
             {
             {
                 var newValue = change.GetNewValue<bool?>();
                 var newValue = change.GetNewValue<bool?>();
 
 
+                UpdatePseudoClasses(newValue);
+
 #pragma warning disable CS0618 // Type or member is obsolete
 #pragma warning disable CS0618 // Type or member is obsolete
                 switch (newValue)
                 switch (newValue)
                 {
                 {

+ 30 - 29
src/Avalonia.Controls/RadioButton.cs

@@ -98,31 +98,22 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
-        public static readonly DirectProperty<RadioButton, string?> GroupNameProperty =
-            AvaloniaProperty.RegisterDirect<RadioButton, string?>(
-                nameof(GroupName),
-                o => o.GroupName,
-                (o, v) => o.GroupName = v);
+        public static readonly StyledProperty<string?> GroupNameProperty =
+            AvaloniaProperty.Register<RadioButton, string?>(nameof(GroupName));
 
 
-        private string? _groupName;
         private RadioButtonGroupManager? _groupManager;
         private RadioButtonGroupManager? _groupManager;
 
 
-        public RadioButton()
-        {
-            this.GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
-        }
-
         public string? GroupName
         public string? GroupName
         {
         {
-            get { return _groupName; }
-            set { SetGroupName(value); }
+            get => GetValue(GroupNameProperty);
+            set => SetValue(GroupNameProperty, value);
         }
         }
 
 
         protected override void Toggle()
         protected override void Toggle()
         {
         {
             if (!IsChecked.GetValueOrDefault())
             if (!IsChecked.GetValueOrDefault())
             {
             {
-                IsChecked = true;
+                SetCurrentValue(IsCheckedProperty, true);
             }
             }
         }
         }
 
 
@@ -154,28 +145,38 @@ namespace Avalonia.Controls
             return new RadioButtonAutomationPeer(this);
             return new RadioButtonAutomationPeer(this);
         }
         }
 
 
-        private void SetGroupName(string? newGroupName)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
         {
-            var oldGroupName = GroupName;
-            if (newGroupName != oldGroupName)
+            base.OnPropertyChanged(change);
+
+            if (change.Property == IsCheckedProperty)
             {
             {
-                if (!string.IsNullOrEmpty(oldGroupName))
-                {
-                    _groupManager?.Remove(this, oldGroupName);
-                }
-                _groupName = newGroupName;
-                if (!string.IsNullOrEmpty(newGroupName))
+                IsCheckedChanged(change.GetNewValue<bool?>());
+            }
+            else if (change.Property == GroupNameProperty)
+            {
+                var (oldValue, newValue) = change.GetOldAndNewValue<string?>();
+                OnGroupNameChanged(oldValue, newValue);
+            }
+        }
+
+        private void OnGroupNameChanged(string? oldGroupName, string? newGroupName)
+        {
+            if (!string.IsNullOrEmpty(oldGroupName))
+            {
+                _groupManager?.Remove(this, oldGroupName);
+            }
+            if (!string.IsNullOrEmpty(newGroupName))
+            {
+                if (_groupManager == null)
                 {
                 {
-                    if (_groupManager == null)
-                    {
-                        _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(this.GetVisualRoot());
-                    }
-                    _groupManager.Add(this);
+                    _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(this.GetVisualRoot());
                 }
                 }
+                _groupManager.Add(this);
             }
             }
         }
         }
 
 
-        private void IsCheckedChanged(bool? value)
+        private new void IsCheckedChanged(bool? value)
         {
         {
             var groupName = GroupName;
             var groupName = GroupName;
             if (string.IsNullOrEmpty(groupName))
             if (string.IsNullOrEmpty(groupName))

+ 4 - 8
src/Avalonia.Controls/SplitButton/SplitButton.cs

@@ -42,10 +42,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Command"/> property.
         /// Defines the <see cref="Command"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<SplitButton, ICommand?> CommandProperty =
-            Button.CommandProperty.AddOwner<SplitButton>(
-                splitButton => splitButton.Command,
-                (splitButton, command) => splitButton.Command = command);
+        public static readonly StyledProperty<ICommand?> CommandProperty =
+            Button.CommandProperty.AddOwner<SplitButton>();
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="CommandParameter"/> property.
         /// Defines the <see cref="CommandParameter"/> property.
@@ -59,8 +57,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<FlyoutBase?> FlyoutProperty =
         public static readonly StyledProperty<FlyoutBase?> FlyoutProperty =
             Button.FlyoutProperty.AddOwner<SplitButton>();
             Button.FlyoutProperty.AddOwner<SplitButton>();
 
 
-        private ICommand? _Command;
-
         private Button? _primaryButton   = null;
         private Button? _primaryButton   = null;
         private Button? _secondaryButton = null;
         private Button? _secondaryButton = null;
 
 
@@ -83,8 +79,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public ICommand? Command
         public ICommand? Command
         {
         {
-            get => _Command;
-            set => SetAndRaise(CommandProperty, ref _Command, value);
+            get => GetValue(CommandProperty);
+            set => SetValue(CommandProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 4 - 1
src/Avalonia.Controls/TopLevel.cs

@@ -15,6 +15,7 @@ using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Platform.Storage;
 using Avalonia.Platform.Storage;
+using Avalonia.Reactive;
 using Avalonia.Rendering;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
@@ -391,7 +392,9 @@ namespace Avalonia.Controls
             ??= AvaloniaLocator.Current.GetService<IStorageProviderFactory>()?.CreateProvider(this)
             ??= AvaloniaLocator.Current.GetService<IStorageProviderFactory>()?.CreateProvider(this)
             ?? PlatformImpl?.TryGetFeature<IStorageProvider>()
             ?? PlatformImpl?.TryGetFeature<IStorageProvider>()
             ?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
             ?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
-        
+
+        public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature<IInsetsManager>();
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         Point IRenderRoot.PointToClient(PixelPoint p)
         Point IRenderRoot.PointToClient(PixelPoint p)
         {
         {

+ 4 - 10
src/Avalonia.Controls/TrayIcon.cs

@@ -13,13 +13,10 @@ namespace Avalonia.Controls
     public sealed class TrayIcons : AvaloniaList<TrayIcon>
     public sealed class TrayIcons : AvaloniaList<TrayIcon>
     {
     {
     }
     }
-    
-    
 
 
     public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
     public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
     {
     {
         private readonly ITrayIconImpl? _impl;
         private readonly ITrayIconImpl? _impl;
-        private ICommand? _command;
 
 
         private TrayIcon(ITrayIconImpl? impl)
         private TrayIcon(ITrayIconImpl? impl)
         {
         {
@@ -85,11 +82,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Command"/> property.
         /// Defines the <see cref="Command"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<TrayIcon, ICommand?> CommandProperty =
-            Button.CommandProperty.AddOwner<TrayIcon>(
-                trayIcon => trayIcon.Command,
-                (trayIcon, command) => trayIcon.Command = command,
-                enableDataValidation: true);
+        public static readonly StyledProperty<ICommand?> CommandProperty =
+            Button.CommandProperty.AddOwner<TrayIcon>(new(enableDataValidation: true));
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="CommandParameter"/> property.
         /// Defines the <see cref="CommandParameter"/> property.
@@ -136,8 +130,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public ICommand? Command
         public ICommand? Command
         {
         {
-            get => _command;
-            set => SetAndRaise(CommandProperty, ref _command, value);
+            get => GetValue(CommandProperty);
+            set => SetValue(CommandProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 2
src/Avalonia.Controls/TreeViewItem.cs

@@ -104,12 +104,12 @@ namespace Avalonia.Controls
 
 
             if (ItemTemplate == null && _treeView?.ItemTemplate != null)
             if (ItemTemplate == null && _treeView?.ItemTemplate != null)
             {
             {
-                ItemTemplate = _treeView.ItemTemplate;
+                SetCurrentValue(ItemTemplateProperty, _treeView.ItemTemplate);
             }
             }
 
 
             if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null)
             if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null)
             {
             {
-                ItemContainerTheme = _treeView.ItemContainerTheme;
+                SetCurrentValue(ItemContainerThemeProperty, _treeView.ItemContainerTheme);
             }
             }
         }
         }
 
 

+ 15 - 20
src/Avalonia.Controls/Window.cs

@@ -1,8 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Linq;
 using System.Linq;
-using Avalonia.Reactive;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Avalonia.Automation.Peers;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Platform;
@@ -11,6 +9,7 @@ using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Platform;
+using Avalonia.Reactive;
 using Avalonia.Styling;
 using Avalonia.Styling;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
@@ -149,11 +148,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Defines the <see cref="WindowStartupLocation"/> property.
         /// Defines the <see cref="WindowStartupLocation"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<Window, WindowStartupLocation> WindowStartupLocationProperty =
-            AvaloniaProperty.RegisterDirect<Window, WindowStartupLocation>(
-                nameof(WindowStartupLocation),
-                o => o.WindowStartupLocation,
-                (o, v) => o.WindowStartupLocation = v);
+        public static readonly StyledProperty<WindowStartupLocation> WindowStartupLocationProperty =
+            AvaloniaProperty.Register<Window, WindowStartupLocation>(nameof(WindowStartupLocation));
 
 
         public static readonly StyledProperty<bool> CanResizeProperty =
         public static readonly StyledProperty<bool> CanResizeProperty =
             AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
             AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
@@ -171,7 +167,6 @@ namespace Avalonia.Controls
             RoutedEvent.Register<Window, RoutedEventArgs>("WindowOpened", RoutingStrategies.Direct);
             RoutedEvent.Register<Window, RoutedEventArgs>("WindowOpened", RoutingStrategies.Direct);
         private object? _dialogResult;
         private object? _dialogResult;
         private readonly Size _maxPlatformClientSize;
         private readonly Size _maxPlatformClientSize;
-        private WindowStartupLocation _windowStartupLocation;
         private bool _shown;
         private bool _shown;
         private bool _showingAsDialog;
         private bool _showingAsDialog;
 
 
@@ -305,7 +300,7 @@ namespace Avalonia.Controls
         {
         {
             get => GetValue(ExtendClientAreaTitleBarHeightHintProperty);
             get => GetValue(ExtendClientAreaTitleBarHeightHintProperty);
             set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value);
             set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value);
-        }        
+        }
 
 
         /// <summary>
         /// <summary>
         /// Gets if the ClientArea is Extended into the Window Decorations.
         /// Gets if the ClientArea is Extended into the Window Decorations.
@@ -314,7 +309,7 @@ namespace Avalonia.Controls
         {
         {
             get => _isExtendedIntoWindowDecorations;
             get => _isExtendedIntoWindowDecorations;
             private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value);
             private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value);
-        }        
+        }
 
 
         /// <summary>
         /// <summary>
         /// Gets the WindowDecorationMargin.
         /// Gets the WindowDecorationMargin.
@@ -324,7 +319,7 @@ namespace Avalonia.Controls
         {
         {
             get => _windowDecorationMargin;
             get => _windowDecorationMargin;
             private set => SetAndRaise(WindowDecorationMarginProperty, ref _windowDecorationMargin, value);
             private set => SetAndRaise(WindowDecorationMarginProperty, ref _windowDecorationMargin, value);
-        }        
+        }
 
 
         /// <summary>
         /// <summary>
         /// Gets the window margin that is hidden off the screen area.
         /// Gets the window margin that is hidden off the screen area.
@@ -397,8 +392,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public WindowStartupLocation WindowStartupLocation
         public WindowStartupLocation WindowStartupLocation
         {
         {
-            get { return _windowStartupLocation; }
-            set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLocation, value); }
+            get => GetValue(WindowStartupLocationProperty);
+            set => SetValue(WindowStartupLocationProperty, value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -488,7 +483,7 @@ namespace Avalonia.Controls
                 CloseInternal();
                 CloseInternal();
                 return false;
                 return false;
             }
             }
-            
+
             return true;
             return true;
         }
         }
 
 
@@ -614,7 +609,7 @@ namespace Avalonia.Controls
 
 
                 if (_shown != isVisible)
                 if (_shown != isVisible)
                 {
                 {
-                    if(!_shown)
+                    if (!_shown)
                     {
                     {
                         Show();
                         Show();
                     }
                     }
@@ -657,7 +652,7 @@ namespace Avalonia.Controls
                 throw new InvalidOperationException("Cannot re-show a closed window.");
                 throw new InvalidOperationException("Cannot re-show a closed window.");
             }
             }
         }
         }
-        
+
         private void EnsureParentStateBeforeShow(Window owner)
         private void EnsureParentStateBeforeShow(Window owner)
         {
         {
             if (owner.PlatformImpl == null)
             if (owner.PlatformImpl == null)
@@ -819,7 +814,7 @@ namespace Avalonia.Controls
         {
         {
             bool isEnabled = true;
             bool isEnabled = true;
 
 
-            foreach (var (_, isDialog)  in _children)
+            foreach (var (_, isDialog) in _children)
             {
             {
                 if (isDialog)
                 if (isDialog)
                 {
                 {
@@ -856,7 +851,7 @@ namespace Avalonia.Controls
         {
         {
             Window? firstDialogChild = null;
             Window? firstDialogChild = null;
 
 
-            foreach (var (child, isDialog)  in _children)
+            foreach (var (child, isDialog) in _children)
             {
             {
                 if (isDialog)
                 if (isDialog)
                 {
                 {
@@ -880,7 +875,7 @@ namespace Avalonia.Controls
             var startupLocation = WindowStartupLocation;
             var startupLocation = WindowStartupLocation;
 
 
             if (startupLocation == WindowStartupLocation.CenterOwner &&
             if (startupLocation == WindowStartupLocation.CenterOwner &&
-                (owner is null || 
+                (owner is null ||
                  (Owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized))
                  (Owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized))
                 )
                 )
             {
             {
@@ -902,7 +897,7 @@ namespace Avalonia.Controls
 
 
                 if (owner is not null)
                 if (owner is not null)
                 {
                 {
-                    screen = Screens.ScreenFromWindow(owner) 
+                    screen = Screens.ScreenFromWindow(owner)
                              ?? Screens.ScreenFromPoint(owner.Position);
                              ?? Screens.ScreenFromPoint(owner.Position);
                 }
                 }
 
 

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