Explorar o código

Merge branch 'master' into fixes/macos-noresize-fullscreen

Steven Kirk %!s(int64=2) %!d(string=hai) anos
pai
achega
efea5e7ef2
Modificáronse 100 ficheiros con 896 adicións e 672 borrados
  1. 11 10
      .editorconfig
  2. 1 0
      Avalonia.Desktop.slnf
  3. 3 3
      build/HarfBuzzSharp.props
  4. 1 1
      build/ImageSharp.props
  5. 1 1
      build/Moq.props
  6. 1 0
      build/SharedVersion.props
  7. 3 3
      build/SkiaSharp.props
  8. 7 8
      build/XUnit.props
  9. 4 0
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  10. 39 0
      native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm
  11. 1 0
      native/Avalonia.Native/src/OSX/common.h
  12. 11 0
      native/Avalonia.Native/src/OSX/main.mm
  13. 2 0
      readme.md
  14. 2 2
      samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
  15. 6 4
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  16. 1 1
      samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
  17. 1 1
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  18. 21 11
      samples/GpuInterop/VulkanDemo/VulkanImage.cs
  19. 1 0
      samples/IntegrationTestApp/MainWindow.axaml
  20. 2 2
      samples/MobileSandbox/MainView.xaml
  21. 9 2
      samples/interop/WindowsInteropTest/Program.cs
  22. 2 5
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  23. 8 34
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  24. 1 0
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  25. 127 0
      src/Android/Avalonia.Android/InputEditable.cs
  26. 1 1
      src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
  27. 1 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  28. 28 113
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  29. 2 2
      src/Android/Avalonia.Android/PlatformIconLoader.cs
  30. 3 3
      src/Android/Avalonia.Android/Stubs.cs
  31. 4 7
      src/Avalonia.Base/Animation/KeySpline.cs
  32. 71 27
      src/Avalonia.Base/AvaloniaObject.cs
  33. 42 8
      src/Avalonia.Base/AvaloniaProperty.cs
  34. 1 1
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  35. 0 3
      src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs
  36. 12 11
      src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
  37. 5 0
      src/Avalonia.Base/DirectPropertyBase.cs
  38. 5 13
      src/Avalonia.Base/Input/DragEventArgs.cs
  39. 1 1
      src/Avalonia.Base/Input/KeyGesture.cs
  40. 19 25
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  41. 15 33
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  42. 3 3
      src/Avalonia.Base/Input/Platform/IClipboard.cs
  43. 23 0
      src/Avalonia.Base/Input/TextInput/ITextEditable.cs
  44. 11 0
      src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
  45. 3 3
      src/Avalonia.Base/LogicalTree/LogicalExtensions.cs
  46. 3 8
      src/Avalonia.Base/Media/Color.cs
  47. 1 1
      src/Avalonia.Base/Media/DrawingContext.cs
  48. 13 13
      src/Avalonia.Base/Media/DrawingGroup.cs
  49. 3 3
      src/Avalonia.Base/Media/DrawingImage.cs
  50. 2 2
      src/Avalonia.Base/Media/FontFamily.cs
  51. 1 4
      src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs
  52. 3 3
      src/Avalonia.Base/Media/FormattedText.cs
  53. 3 3
      src/Avalonia.Base/Media/GeometryDrawing.cs
  54. 6 6
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  55. 1 1
      src/Avalonia.Base/Media/HslColor.cs
  56. 1 1
      src/Avalonia.Base/Media/HsvColor.cs
  57. 1 2
      src/Avalonia.Base/Media/IVisualBrush.cs
  58. 6 18
      src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
  59. 3 4
      src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
  60. 7 7
      src/Avalonia.Base/Media/TextDecoration.cs
  61. 10 7
      src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
  62. 3 4
      src/Avalonia.Base/Media/VisualBrush.cs
  63. 2 2
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  64. 12 0
      src/Avalonia.Base/Platform/IPlatformBehaviorInhibition.cs
  65. 1 1
      src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
  66. 15 18
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
  67. 2 2
      src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs
  68. 23 24
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  69. 6 0
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  70. 18 11
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  71. 37 12
      src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
  72. 18 7
      src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
  73. 2 0
      src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
  74. 2 0
      src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
  75. 5 0
      src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs
  76. 23 28
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  77. 1 1
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  78. 1 1
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  79. 1 1
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  80. 58 24
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  81. 1 2
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  82. 2 2
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  83. 1 1
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  84. 12 2
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  85. 0 2
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  86. 0 1
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
  87. 2 2
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  88. 2 3
      src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
  89. 4 1
      src/Avalonia.Base/Rendering/IRenderLoop.cs
  90. 0 2
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  91. 0 1
      src/Avalonia.Base/Rendering/IRenderTimer.cs
  92. 3 0
      src/Avalonia.Base/Rendering/IRenderer.cs
  93. 4 2
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  94. 1 0
      src/Avalonia.Base/Rendering/RenderLoop.cs
  95. 2 5
      src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
  96. 8 1
      src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs
  97. 4 0
      src/Avalonia.Base/StyledElement.cs
  98. 30 14
      src/Avalonia.Base/StyledProperty.cs
  99. 3 8
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  100. 7 61
      src/Avalonia.Base/Utilities/WeakEvent.cs

+ 11 - 10
.editorconfig

@@ -55,16 +55,17 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const
 
 dotnet_naming_style.pascal_case_style.capitalization = pascal_case
 
-# static fields should have s_ prefix
-dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
-dotnet_naming_rule.static_fields_should_have_prefix.symbols  = static_fields
-dotnet_naming_rule.static_fields_should_have_prefix.style    = static_prefix_style
+# private static fields should have s_ prefix
+dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_have_prefix.symbols  = private_static_fields
+dotnet_naming_rule.private_static_fields_should_have_prefix.style    = private_static_prefix_style
 
-dotnet_naming_symbols.static_fields.applicable_kinds   = field
-dotnet_naming_symbols.static_fields.required_modifiers = static
+dotnet_naming_symbols.private_static_fields.applicable_kinds   = field
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
 
-dotnet_naming_style.static_prefix_style.required_prefix = s_
-dotnet_naming_style.static_prefix_style.capitalization = camel_case
+dotnet_naming_style.private_static_prefix_style.required_prefix = s_
+dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
 
 # internal and private fields should be _camelCase
 dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
@@ -117,7 +118,7 @@ csharp_space_after_dot = false
 csharp_space_after_keywords_in_control_flow_statements = true
 csharp_space_after_semicolon_in_for_statement = true
 csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_around_declaration_statements = false
 csharp_space_before_colon_in_inheritance_clause = true
 csharp_space_before_comma = false
 csharp_space_before_dot = false
@@ -211,5 +212,5 @@ indent_size = 2
 # Shell scripts
 [*.sh]
 end_of_line = lf
-[*.{cmd, bat}]
+[*.{cmd,bat}]
 end_of_line = crlf

+ 1 - 0
Avalonia.Desktop.slnf

@@ -45,6 +45,7 @@
       "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
       "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
       "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
+      "tests\\Avalonia.Controls.ItemsRepeater.UnitTests\\Avalonia.Controls.ItemsRepeater.UnitTests.csproj",
       "tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj",
       "tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj",
       "tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",

+ 3 - 3
build/HarfBuzzSharp.props

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

+ 1 - 1
build/ImageSharp.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/Moq.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="Moq" Version="4.14.1" />
+    <PackageReference Include="Moq" Version="4.18.4" />
   </ItemGroup>
 </Project>

+ 1 - 0
build/SharedVersion.props

@@ -3,6 +3,7 @@
   <PropertyGroup>
     <Product>Avalonia</Product>
     <Version>11.0.999</Version>
+    <Authors>Avalonia Team</Authors>
     <Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright>
     <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
     <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

+ 3 - 3
build/SkiaSharp.props

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

+ 7 - 8
build/XUnit.props

@@ -1,13 +1,12 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="xunit" Version="2.4.1" />
-    <PackageReference Include="xunit.abstractions" Version="2.0.3" />
-    <PackageReference Include="xunit.assert" Version="2.4.1" />
-    <PackageReference Include="xunit.core" Version="2.4.1" />
-    <PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
-    <PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
-    <PackageReference Include="xunit.runner.console" Version="2.4.1" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+    <PackageReference Include="xunit" Version="2.4.2" />
+    <PackageReference Include="xunit.assert" Version="2.4.2" />
+    <PackageReference Include="xunit.core" Version="2.4.2" />
+    <PackageReference Include="xunit.extensibility.core" Version="2.4.2" />
+    <PackageReference Include="xunit.extensibility.execution" Version="2.4.2" />
+    <PackageReference Include="xunit.runner.console" Version="2.4.2" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
     <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
   </ItemGroup>

+ 4 - 0
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@@ -43,6 +43,7 @@
 		523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
 		5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
 		5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
+		855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */; };
 		AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
 		AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
 		AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
@@ -95,6 +96,7 @@
 		5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
 		5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
 		5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
+		855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = "<group>"; };
 		AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
 		AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
 		AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
@@ -140,6 +142,7 @@
 		AB7A61E62147C814003C5833 = {
 			isa = PBXGroup;
 			children = (
+				855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */,
 				BC11A5BC2608D58F0017BAD0 /* automation.h */,
 				BC11A5BD2608D58F0017BAD0 /* automation.mm */,
 				1A1852DB23E05814008F0DED /* deadlock.mm */,
@@ -288,6 +291,7 @@
 				1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
 				BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */,
 				37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
+				855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */,
 				520624B322973F4100C4DCEF /* menu.mm in Sources */,
 				37A517B32159597E00FBA241 /* Screens.mm in Sources */,
 				1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,

+ 39 - 0
native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm

@@ -0,0 +1,39 @@
+#include "common.h"
+
+namespace
+{
+    id<NSObject> s_inhibitAppSleepHandle{};
+}
+
+class PlatformBehaviorInhibition : public ComSingleObject<IAvnPlatformBehaviorInhibition, &IID_IAvnCursorFactory>
+{
+public:
+    FORWARD_IUNKNOWN()
+    
+    virtual void SetInhibitAppSleep(bool inhibitAppSleep, char* reason) override
+    {
+        START_COM_CALL;
+        
+        @autoreleasepool
+        {
+            if (inhibitAppSleep && s_inhibitAppSleepHandle == nullptr)
+            {
+                NSActivityOptions options = NSActivityUserInitiatedAllowingIdleSystemSleep;
+                s_inhibitAppSleepHandle = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:[NSString stringWithUTF8String: reason]];
+            }
+            
+            if (!inhibitAppSleep)
+            {
+                s_inhibitAppSleepHandle = nullptr;
+            }
+        }
+    }
+};
+
+extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition()
+{
+    @autoreleasepool
+    {
+        return new PlatformBehaviorInhibition();
+    }
+}

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

@@ -26,6 +26,7 @@ extern IAvnTrayIcon* CreateTrayIcon();
 extern IAvnMenuItem* CreateAppMenuItem();
 extern IAvnMenuItem* CreateAppMenuItemSeparator();
 extern IAvnApplicationCommands* CreateApplicationCommands();
+extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition();
 extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
 extern IAvnPlatformSettings* CreatePlatformSettings();
 extern void SetAppMenu(IAvnMenu *menu);

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

@@ -408,6 +408,17 @@ public:
             return S_OK;
         }
     }
+
+    virtual HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv) override
+    {
+        START_COM_CALL;
+        
+        @autoreleasepool
+        {
+            *ppv = ::CreatePlatformBehaviorInhibition();
+            return S_OK;
+        }
+    }
 };
 
 extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

+ 2 - 0
readme.md

@@ -1,3 +1,5 @@
+[![GH_Banner](https://user-images.githubusercontent.com/552074/218457976-92e76834-9e22-4e35-acfa-aa50281bc0f9.png)](https://avaloniaui.net/xpf)
+
 [![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia)
 [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg)
 <br />

+ 2 - 2
samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj

@@ -9,8 +9,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0-rc.1.22427.2" />
-    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0-rc.1.22427.2" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.2" />
+    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.2" PrivateAssets="all" />
   </ItemGroup>
 
   <ItemGroup>

+ 6 - 4
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@@ -59,10 +59,12 @@ namespace ControlCatalog.Pages
 
             };
             StreamGeometry sg = new StreamGeometry();
-            var cntx = sg.Open();
-            cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
-            cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
-            cntx.EndFigure(true);
+            using (var cntx = sg.Open())
+            {
+                cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
+                cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
+                cntx.EndFigure(true);
+            }
             _smileGeometry = sg.Clone();
         }
 

+ 1 - 1
samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs

@@ -47,7 +47,7 @@ public class D3DMemoryHelper
                 MipLevels = 1,
                 SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
                 CpuAccessFlags = default,
-                OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
+                OptionFlags = ResourceOptionFlags.SharedKeyedmutex|ResourceOptionFlags.SharedNthandle,
                 BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
             });
     }

+ 1 - 1
samples/GpuInterop/VulkanDemo/VulkanContext.cs

@@ -173,7 +173,7 @@ public unsafe class VulkanContext : IDisposable
                 api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties);
                 for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
                 {
-                    var family = familyProperties[c];
+                    var family = familyProperties[queueFamilyIndex];
                     if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit))
                         continue;
 

+ 21 - 11
samples/GpuInterop/VulkanDemo/VulkanImage.cs

@@ -4,10 +4,13 @@ using System.Runtime.InteropServices;
 using Avalonia;
 using Avalonia.Platform;
 using Avalonia.Vulkan;
+using SharpDX.DXGI;
 using Silk.NET.Vulkan;
 using Silk.NET.Vulkan.Extensions.KHR;
 using SilkNetDemo;
 using SkiaSharp;
+using Device = Silk.NET.Vulkan.Device;
+using Format = Silk.NET.Vulkan.Format;
 
 namespace GpuInterop.VulkanDemo;
 
@@ -24,7 +27,6 @@ public unsafe class VulkanImage : IDisposable
         private ImageView? _imageView { get; set; }
         private DeviceMemory _imageMemory { get; set; }
         private SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
-        private IntPtr _win32ShareHandle;
         
         internal Image? InternalHandle { get; private set; }
         internal Format Format { get; }
@@ -60,7 +62,7 @@ public unsafe class VulkanImage : IDisposable
             //MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
 
             var handleType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
-                ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit :
+                ExternalMemoryHandleTypeFlags.D3D11TextureBit :
                 ExternalMemoryHandleTypeFlags.OpaqueFDBit;
             var externalMemoryCreateInfo = new ExternalMemoryImageCreateInfo
             {
@@ -108,14 +110,14 @@ public unsafe class VulkanImage : IDisposable
             if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
                  _d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format);
-                 using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource>();
-                 _win32ShareHandle = dxgi.SharedHandle;
+                 using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource1>();
+                 
                  handleImport = new ImportMemoryWin32HandleInfoKHR
                  {
                      PNext = &dedicatedAllocation,
                      SType = StructureType.ImportMemoryWin32HandleInfoKhr,
-                     HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit,
-                     Handle = _win32ShareHandle,
+                     HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit,
+                     Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
                  };
             }
 
@@ -185,11 +187,19 @@ public unsafe class VulkanImage : IDisposable
             return fd;
         }
         
-        public IPlatformHandle Export() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
-            new PlatformHandle(_win32ShareHandle,
-                KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) :
-            new PlatformHandle(new IntPtr(ExportFd()),
-                KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
+        public IPlatformHandle Export()
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                using var dxgi = _d3dTexture2D!.QueryInterface<Resource1>();
+                return new PlatformHandle(
+                    dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
+                    KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle);
+            }
+            else
+                return new PlatformHandle(new IntPtr(ExportFd()),
+                    KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
+        }
 
         public ImageTiling Tiling => ImageTiling.Optimal;
 

+ 1 - 0
samples/IntegrationTestApp/MainWindow.axaml

@@ -70,6 +70,7 @@
             <ComboBoxItem>Item 0</ComboBoxItem>
             <ComboBoxItem>Item 1</ComboBoxItem>
           </ComboBox>
+          <CheckBox Name="ComboBoxWrapSelection" IsChecked="{Binding #BasicComboBox.WrapSelection}">Wrap Selection</CheckBox>
           <Button Name="ComboBoxSelectionClear">Clear Selection</Button>
           <Button Name="ComboBoxSelectFirst">Select First</Button>
         </StackPanel>

+ 2 - 2
samples/MobileSandbox/MainView.xaml

@@ -5,8 +5,8 @@
              x:DataType="mobileSandbox:MainView">
   <StackPanel Margin="100 50" Spacing="50">
     <TextBlock Text="Login" Foreground="White" />
-    <TextBox Watermark="Text" />
-    <TextBox Watermark="Username" TextInputOptions.ContentType="Email" AcceptsReturn="True" TextInputOptions.ReturnKeyType="Search" />
+    <TextBox TextInputOptions.Multiline="True" AcceptsReturn="True" Watermark="Text" Height="200" TextWrapping="Wrap"/>
+    <TextBox Watermark="Username" TextInputOptions.ContentType="Email" TextInputOptions.ReturnKeyType="Done" />
     <TextBox Watermark="Password" PasswordChar="*" TextInputOptions.ContentType="Password" />
     <TextBox Watermark="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" />
     <Button Content="Login" Command="{Binding ButtonCommand}" />

+ 9 - 2
samples/interop/WindowsInteropTest/Program.cs

@@ -1,5 +1,4 @@
 using System;
-using Avalonia.Controls;
 using ControlCatalog;
 using Avalonia;
 
@@ -15,7 +14,15 @@ namespace WindowsInteropTest
         {
             System.Windows.Forms.Application.EnableVisualStyles();
             System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
-            AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting();
+            AppBuilder.Configure<App>()
+                .UseWin32()
+                .UseDirect2D1()
+                .With(new Win32PlatformOptions
+                {
+                    UseWindowsUIComposition = false,
+                    ShouldRenderOnUIThread = true // necessary for WPF
+                })
+                .SetupWithoutStarting();
             System.Windows.Forms.Application.Run(new SelectorForm());
         }
     }

+ 2 - 5
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@@ -2,7 +2,7 @@
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
     <TargetFramework>net461</TargetFramework>
-
+    <PlatformTarget>x64</PlatformTarget>
     <UseWPF>true</UseWPF>
     <UseWindowsForms>true</UseWindowsForms>
   </PropertyGroup>
@@ -10,9 +10,6 @@
   <ItemGroup>
     <ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
     <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj" />
-    <ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj">
-      <Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
-      <Name>ControlCatalog</Name>
-    </ProjectReference>
+    <ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
 </Project>

+ 8 - 34
src/Android/Avalonia.Android/AndroidInputMethod.cs

@@ -5,8 +5,10 @@ using Android.Text;
 using Android.Views;
 using Android.Views.InputMethods;
 using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Presenters;
 using Avalonia.Input;
 using Avalonia.Input.TextInput;
+using Avalonia.Reactive;
 
 namespace Avalonia.Android
 {
@@ -32,7 +34,7 @@ namespace Avalonia.Android
        ActionPrevious = 0x00000007,
     }
 
-    class AndroidInputMethod<TView> : ITextInputMethodImpl, IAndroidInputMethod
+    internal class AndroidInputMethod<TView> : ITextInputMethodImpl, IAndroidInputMethod
         where TView : View, IInitEditorInfo
     {
         private readonly TView _host;
@@ -68,23 +70,10 @@ namespace Avalonia.Android
 
         public void SetClient(ITextInputMethodClient client)
         {
-            if (_client != null)
-            {
-                _client.SurroundingTextChanged -= SurroundingTextChanged;
-            }
-
-            if(_inputConnection != null)
-            {
-                _inputConnection.ComposingText = null;
-                _inputConnection.ComposingRegion = default;
-            }
-
             _client = client;
 
             if (IsActive)
             {
-                _client.SurroundingTextChanged += SurroundingTextChanged;
-
                 _host.RequestFocus();
 
                 _imm.RestartInput(View);              
@@ -101,24 +90,6 @@ namespace Avalonia.Android
             }
         }
 
-        private void SurroundingTextChanged(object sender, EventArgs e)
-        {
-            if (IsActive && _inputConnection != null)
-            {
-                var surroundingText = Client.SurroundingText;
-
-                _inputConnection.SurroundingText = surroundingText;
-
-                _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
-
-                if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset)
-                {
-                    _inputConnection.CommitText(_inputConnection.ComposingText, 0);
-                    _inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset);
-                }
-            }
-        }
-
         public void SetCursorRect(Rect rect)
         {
             
@@ -157,17 +128,20 @@ namespace Avalonia.Android
                     TextInputReturnKeyType.Search => (ImeFlags)CustomImeFlags.ActionSearch,
                     TextInputReturnKeyType.Next => (ImeFlags)CustomImeFlags.ActionNext,
                     TextInputReturnKeyType.Previous => (ImeFlags)CustomImeFlags.ActionPrevious,
-                    _ => (ImeFlags)CustomImeFlags.ActionDone
+                    TextInputReturnKeyType.Done => (ImeFlags)CustomImeFlags.ActionDone,
+                    _ => options.Multiline ? ImeFlags.NoEnterAction : (ImeFlags)CustomImeFlags.ActionDone
                 };
 
                 outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
 
+                _client.TextEditable = _inputConnection.InputEditable;
+
                 return _inputConnection;
             });
         }
     }
 
-    public readonly record struct ComposingRegion
+    internal readonly record struct ComposingRegion
     {
         private readonly int _start = -1;
         private readonly int _end = -1;

+ 1 - 0
src/Android/Avalonia.Android/Avalonia.Android.csproj

@@ -5,6 +5,7 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
     <DebugType>portable</DebugType>
+    <AndroidResgenNamespace>Avalonia.Android.Internal</AndroidResgenNamespace>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />

+ 127 - 0
src/Android/Avalonia.Android/InputEditable.cs

@@ -0,0 +1,127 @@
+using System;
+using Android.Runtime;
+using Android.Text;
+using Android.Views;
+using Android.Views.InputMethods;
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Presenters;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Input.TextInput;
+using Java.Lang;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace Avalonia.Android
+{
+    internal class InputEditable : SpannableStringBuilder, ITextEditable
+    {
+        private readonly TopLevelImpl _topLevel;
+        private readonly IAndroidInputMethod _inputMethod;
+        private readonly AvaloniaInputConnection _avaloniaInputConnection;
+        private int _currentBatchLevel;
+        private string _previousText;
+        private int _previousSelectionStart;
+        private int _previousSelectionEnd;
+
+        public event EventHandler TextChanged;
+        public event EventHandler SelectionChanged;
+        public event EventHandler CompositionChanged;
+
+        public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod, AvaloniaInputConnection avaloniaInputConnection)
+        {
+            _topLevel = topLevel;
+            _inputMethod = inputMethod;
+            _avaloniaInputConnection = avaloniaInputConnection;
+        }
+
+        public InputEditable(ICharSequence text) : base(text)
+        {
+        }
+
+        public InputEditable(string text) : base(text)
+        {
+        }
+
+        public InputEditable(ICharSequence text, int start, int end) : base(text, start, end)
+        {
+        }
+
+        public InputEditable(string text, int start, int end) : base(text, start, end)
+        {
+        }
+
+        protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
+        {
+        }
+
+        public int SelectionStart
+        {
+            get => Selection.GetSelectionStart(this); set
+            {
+                var end = SelectionEnd < 0 ? 0 : SelectionEnd;
+                _avaloniaInputConnection.SetSelection(value, end);
+                _inputMethod.IMM.UpdateSelection(_topLevel.View, value, end, value, end);
+            }
+        }
+        public int SelectionEnd
+        {
+            get => Selection.GetSelectionEnd(this); set
+            {
+                var start = SelectionStart < 0 ? 0 : SelectionStart;
+                _avaloniaInputConnection.SetSelection(start, value);
+                _inputMethod.IMM.UpdateSelection(_topLevel.View, start, value, start, value);
+            }
+        }
+
+        public string? Text
+        {
+            get => ToString(); set
+            {
+                if (Text != value)
+                {
+                    Clear();
+                    Insert(0, value ?? "");
+                }
+            }
+        }
+
+        public int CompositionStart => BaseInputConnection.GetComposingSpanStart(this);
+
+        public int CompositionEnd => BaseInputConnection.GetComposingSpanEnd(this);
+
+        public void BeginBatchEdit()
+        {
+            _currentBatchLevel++;
+
+            if (_currentBatchLevel == 1)
+            {
+                _previousText = ToString();
+                _previousSelectionStart =  SelectionStart;
+                _previousSelectionEnd = SelectionEnd;
+            }
+        }
+
+        public void EndBatchEdit()
+        {
+            if (_currentBatchLevel == 1)
+            {
+                if(_previousText != Text)
+                {
+                    TextChanged?.Invoke(this, EventArgs.Empty);
+                }
+
+                if (_previousSelectionStart != SelectionStart || _previousSelectionEnd != SelectionEnd)
+                {
+                    SelectionChanged?.Invoke(this, EventArgs.Empty);
+                }
+            }
+
+            _currentBatchLevel--;
+        }
+
+        public void RaiseCompositionChanged()
+        {
+            CompositionChanged?.Invoke(this, EventArgs.Empty);
+        }
+    }
+}

+ 1 - 1
src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs

@@ -5,7 +5,7 @@ using Avalonia.Input;
 
 namespace Avalonia.Android.Platform.Input
 {
-    public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
+    internal class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
     private static readonly Dictionary<Keycode, Key> KeyDic = new Dictionary<Keycode, Key>
      {
          //   { Keycode.Cancel?, Key.Cancel },

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

@@ -10,7 +10,7 @@ using Avalonia.Platform;
 
 namespace Avalonia.Android
 {
-    public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
+    internal abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
     {
         bool _invalidateQueued;
         readonly object _lock = new object();

+ 28 - 113
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -28,6 +28,7 @@ using Math = System.Math;
 using AndroidRect = Android.Graphics.Rect;
 using Window = Android.Views.Window;
 using Android.Graphics.Drawables;
+using Java.Util;
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
@@ -410,159 +411,73 @@ namespace Avalonia.Android.Platform.SkiaPlatform
     {
         private readonly TopLevelImpl _topLevel;
         private readonly IAndroidInputMethod _inputMethod;
+        private readonly InputEditable _editable;
 
         public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
         {
             _topLevel = topLevel;
             _inputMethod = inputMethod;
+            _editable = new InputEditable(_topLevel, _inputMethod, this);
         }
 
-        public TextInputMethodSurroundingText SurroundingText { get; set; }
+        public override IEditable Editable => _editable;
 
-        public string ComposingText { get; internal set; }
-
-        public ComposingRegion? ComposingRegion { get; internal set; }
-
-        public bool IsComposing => !string.IsNullOrEmpty(ComposingText);
-        public bool IsCommiting { get; private set; }
+        internal InputEditable InputEditable => _editable;
 
         public override bool SetComposingRegion(int start, int end)
         {
-            //System.Diagnostics.Debug.WriteLine($"Composing Region: [{start}|{end}] {SurroundingText.Text?.Substring(start, end - start)}");
+            var ret = base.SetComposingRegion(start, end);
 
-            ComposingRegion = new ComposingRegion(start, end);
+            InputEditable.RaiseCompositionChanged();
 
-            return base.SetComposingRegion(start, end);
+            return ret;
         }
 
         public override bool SetComposingText(ICharSequence text, int newCursorPosition)
         {
             var composingText = text.ToString();
 
-            ComposingText = composingText;
-
-            _inputMethod.Client?.SetPreeditText(ComposingText);
-
-            return base.SetComposingText(text, newCursorPosition);
-        }
-
-        public override bool FinishComposingText()
-        {
-            if (!string.IsNullOrEmpty(ComposingText))
+            if (string.IsNullOrEmpty(composingText))
             {
-                CommitText(ComposingText, ComposingText.Length);
+                return CommitText(text, newCursorPosition);
             }
             else
             {
-                ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
-            }
-
-            return base.FinishComposingText();
-        }
-
-        public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
-        {
-            if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0)
-            {
-                var start = System.Math.Max(SurroundingText.CursorOffset - length, 0);
+                var ret = base.SetComposingText(text, newCursorPosition);
 
-                var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset);
+                InputEditable.RaiseCompositionChanged();
 
-                var text = SurroundingText.Text.Substring(start, end - start);
-
-                //System.Diagnostics.Debug.WriteLine($"Text Before: {text}");
-
-                return new Java.Lang.String(text);
+                return ret;
             }
-
-            return null;
         }
 
-        public override ICharSequence GetTextAfterCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
+        public override bool BeginBatchEdit()
         {
-            if (!string.IsNullOrEmpty(SurroundingText.Text))
-            {
-                var start = SurroundingText.CursorOffset;
-
-                var end = System.Math.Min(start + length, SurroundingText.Text.Length);
-
-                var text = SurroundingText.Text.Substring(start, end - start);
+            _editable.BeginBatchEdit();
 
-                //System.Diagnostics.Debug.WriteLine($"Text After: {text}");
-
-                return new Java.Lang.String(text);
-            }
-
-            return null;
+            return base.BeginBatchEdit();
         }
 
-        public override bool CommitText(ICharSequence text, int newCursorPosition)
+        public override bool EndBatchEdit()
         {
-            IsCommiting = true;
-            var committedText = text.ToString();
-
-            _inputMethod.Client.SetPreeditText(null);
+            var ret = base.EndBatchEdit();
+            _editable.EndBatchEdit();
 
-            int? start, end;
-
-            if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset)
-            {
-                start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
-                end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
-            }
-            else if (ComposingRegion != null)
-            {
-                start = ComposingRegion?.Start;
-                end = ComposingRegion?.End;
-
-                ComposingRegion = null;
-            }
-            else
-            {
-                start = end = _inputMethod.Client.SurroundingText.CursorOffset;
-            }
-
-            _inputMethod.Client.SelectInSurroundingText((int)start, (int)end);
-
-            var time = DateTime.Now.TimeOfDay;
-
-            var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)time.Ticks, _topLevel.InputRoot, committedText);
-
-            _topLevel.Input(rawTextEvent);
-
-            ComposingText = null;
-
-            ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition);
-
-            return base.CommitText(text, newCursorPosition);
+            return ret;
         }
 
-        public override bool DeleteSurroundingText(int beforeLength, int afterLength)
+        public override bool FinishComposingText()
         {
-            var surroundingText = _inputMethod.Client.SurroundingText;
-
-            var selectionStart = surroundingText.CursorOffset;
-
-            _inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength);
-
-            _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
-
-            surroundingText = _inputMethod.Client.SurroundingText;
-
-            selectionStart = surroundingText.CursorOffset;
-
-            ComposingRegion = new ComposingRegion(selectionStart, selectionStart);
-
-            return base.DeleteSurroundingText(beforeLength, afterLength);
+            var ret = base.FinishComposingText();
+            InputEditable.RaiseCompositionChanged();
+            return ret;
         }
 
-        public override bool SetSelection(int start, int end)
+        public override bool CommitText(ICharSequence text, int newCursorPosition)
         {
-            _inputMethod.Client.SelectInSurroundingText(start, end);
-
-            ComposingRegion = new ComposingRegion(start, end);
-
-            return base.SetSelection(start, end);
+            var ret = base.CommitText(text, newCursorPosition);
+            InputEditable.RaiseCompositionChanged();
+            return ret;
         }
 
         public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)

+ 2 - 2
src/Android/Avalonia.Android/PlatformIconLoader.cs

@@ -3,7 +3,7 @@ using Avalonia.Platform;
 
 namespace Avalonia.Android
 {
-    class PlatformIconLoader : IPlatformIconLoader
+    internal class PlatformIconLoader : IPlatformIconLoader
     {
         public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
         {
@@ -29,7 +29,7 @@ namespace Avalonia.Android
     }
 
     // Stores the icon created as a stream to support saving even though an icon is never shown
-    public class FakeIcon : IWindowIconImpl
+    internal class FakeIcon : IWindowIconImpl
     {
         private Stream stream = new MemoryStream();
 

+ 3 - 3
src/Android/Avalonia.Android/Stubs.cs

@@ -4,7 +4,7 @@ using Avalonia.Platform;
 
 namespace Avalonia.Android
 {
-    class WindowingPlatformStub : IWindowingPlatform
+    internal class WindowingPlatformStub : IWindowingPlatform
     {
         public IWindowImpl CreateWindow() => throw new NotSupportedException();
 
@@ -13,7 +13,7 @@ namespace Avalonia.Android
         public ITrayIconImpl CreateTrayIcon() => null;
     }
 
-    class PlatformIconLoaderStub : IPlatformIconLoader
+    internal class PlatformIconLoaderStub : IPlatformIconLoader
     {
         public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
         {
@@ -38,7 +38,7 @@ namespace Avalonia.Android
         }
     }
 
-    public class IconStub : IWindowIconImpl
+    internal class IconStub : IWindowIconImpl
     {
         private readonly MemoryStream _ms;
 

+ 4 - 7
src/Avalonia.Base/Animation/KeySpline.cs

@@ -79,15 +79,12 @@ namespace Avalonia.Animation
         /// <param name="culture">culture of the string</param>
         /// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
         /// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
-        public static KeySpline Parse(string value, CultureInfo culture)
+        public static KeySpline Parse(string value, CultureInfo? culture)
         {
-            if (culture is null)
-                culture = CultureInfo.InvariantCulture;
+            culture ??= CultureInfo.InvariantCulture;
 
-            using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."))
-            {
-                return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
-            }
+            using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".");
+            return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
         }
 
         /// <summary>

+ 71 - 27
src/Avalonia.Base/AvaloniaObject.cs

@@ -118,7 +118,7 @@ namespace Avalonia
         {
             _ = property ?? throw new ArgumentNullException(nameof(property));
             VerifyAccess();
-            _values.ClearLocalValue(property);
+            _values.ClearValue(property);
         }
 
         /// <summary>
@@ -152,7 +152,7 @@ namespace Avalonia
             property = property ?? throw new ArgumentNullException(nameof(property));
             VerifyAccess();
 
-            _values?.ClearLocalValue(property);
+            _values.ClearValue(property);
         }
 
         /// <summary>
@@ -242,7 +242,14 @@ namespace Avalonia
             return registered.InvokeGetter(this);
         }
 
-        /// <inheritdoc/>
+        /// <summary>
+        /// Gets an <see cref="AvaloniaProperty"/> base value.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <remarks>
+        /// Gets the value of the property excluding animated values, otherwise <see cref="Optional{T}.Empty"/>.
+        /// Note that this method does not return property values that come from inherited or default values.
+        /// </remarks>
         public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
         {
             _ = property ?? throw new ArgumentNullException(nameof(property));
@@ -261,7 +268,7 @@ namespace Avalonia
 
             VerifyAccess();
 
-            return _values?.IsAnimating(property) ?? false;
+            return _values.IsAnimating(property);
         }
 
         /// <summary>
@@ -270,8 +277,8 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <returns>True if the property is set, otherwise false.</returns>
         /// <remarks>
-        /// Checks whether a value is assigned to the property, or that there is a binding to the
-        /// property that is producing a value other than <see cref="AvaloniaProperty.UnsetValue"/>.
+        /// Returns true if <paramref name="property"/> is a styled property which has a value
+        /// assigned to it or a binding targeting it; otherwise false.
         /// </remarks>
         public bool IsSet(AvaloniaProperty property)
         {
@@ -279,7 +286,7 @@ namespace Avalonia
 
             VerifyAccess();
 
-            return _values?.IsSet(property) ?? false;
+            return _values.IsSet(property);
         }
 
         /// <summary>
@@ -322,7 +329,7 @@ namespace Avalonia
             if (value is UnsetValueType)
             {
                 if (priority == BindingPriority.LocalValue)
-                    _values.ClearLocalValue(property);
+                    _values.ClearValue(property);
             }
             else if (value is not DoNothingType)
             {
@@ -348,6 +355,57 @@ namespace Avalonia
             SetDirectValueUnchecked(property, value);
         }
 
+        /// <summary>
+        /// Sets the value of a dependency property without changing its value source.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The value.</param>
+        /// <remarks>
+        /// This method is used by a component that programmatically sets the value of one of its
+        /// own properties without disabling an application's declared use of the property. The
+        /// method changes the effective value of the property, but existing data bindings and
+        /// styles will continue to work.
+        /// 
+        /// The new value will have the property's current <see cref="BindingPriority"/>, even if
+        /// that priority is <see cref="BindingPriority.Unset"/> or 
+        /// <see cref="BindingPriority.Inherited"/>.
+        /// </remarks>
+        public void SetCurrentValue(AvaloniaProperty property, object? value) => 
+            property.RouteSetCurrentValue(this, value);
+
+        /// <summary>
+        /// Sets the value of a dependency property without changing its value source.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The value.</param>
+        /// <remarks>
+        /// This method is used by a component that programmatically sets the value of one of its
+        /// own properties without disabling an application's declared use of the property. The
+        /// method changes the effective value of the property, but existing data bindings and
+        /// styles will continue to work.
+        /// 
+        /// The new value will have the property's current <see cref="BindingPriority"/>, even if
+        /// that priority is <see cref="BindingPriority.Unset"/> or 
+        /// <see cref="BindingPriority.Inherited"/>.
+        /// </remarks>
+        public void SetCurrentValue<T>(StyledProperty<T> property, T value)
+        {
+            _ = property ?? throw new ArgumentNullException(nameof(property));
+            VerifyAccess();
+
+            LogPropertySet(property, value, BindingPriority.LocalValue);
+
+            if (value is UnsetValueType)
+            {
+                _values.ClearValue(property);
+            }
+            else if (value is not DoNothingType)
+            {
+                _values.SetCurrentValue(property, value);
+            }
+        }
+
         /// <summary>
         /// Binds a <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
@@ -515,14 +573,12 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
 
-        /// <inheritdoc/>
         internal void AddInheritanceChild(AvaloniaObject child)
         {
             _inheritanceChildren ??= new List<AvaloniaObject>();
             _inheritanceChildren.Add(child);
         }
-        
-        /// <inheritdoc/>
+
         internal void RemoveInheritanceChild(AvaloniaObject child)
         {
             _inheritanceChildren?.Remove(child);
@@ -541,24 +597,12 @@ namespace Avalonia
                 return new AvaloniaPropertyValue(
                     property,
                     GetValue(property),
-                    BindingPriority.Unset,
-                    "Local Value");
+                    BindingPriority.LocalValue,
+                    null,
+                    false);
             }
-            else if (_values != null)
-            {
-                var result = _values.GetDiagnostic(property);
 
-                if (result != null)
-                {
-                    return result;
-                }
-            }
-
-            return new AvaloniaPropertyValue(
-                property,
-                GetValue(property),
-                BindingPriority.Unset,
-                "Unset");
+            return _values.GetDiagnostic(property);
         }
 
         internal ValueStore GetValueStore() => _values;

+ 42 - 8
src/Avalonia.Base/AvaloniaProperty.cs

@@ -225,13 +225,8 @@ namespace Avalonia
         /// <param name="defaultValue">The default value of the property.</param>
         /// <param name="inherits">Whether the property inherits its value.</param>
         /// <param name="defaultBindingMode">The default binding mode for the property.</param>
-         /// <param name="validate">A value validation callback.</param>
+        /// <param name="validate">A value validation callback.</param>
         /// <param name="coerce">A value coercion callback.</param>
-        /// <param name="notifying">
-        /// A method that gets called before and after the property starts being notified on an
-        /// object; the bool argument will be true before and false afterwards. This callback is
-        /// intended to support IsDataContextChanging.
-        /// </param>
         /// <returns>A <see cref="StyledProperty{TValue}"/></returns>
         public static StyledProperty<TValue> Register<TOwner, TValue>(
             string name,
@@ -239,8 +234,40 @@ namespace Avalonia
             bool inherits = false,
             BindingMode defaultBindingMode = BindingMode.OneWay,
             Func<TValue, bool>? validate = null,
-            Func<AvaloniaObject, TValue, TValue>? coerce = null,
-            Action<AvaloniaObject, bool>? notifying = null)
+            Func<AvaloniaObject, TValue, TValue>? coerce = null)
+                where TOwner : AvaloniaObject
+        {
+            _ = name ?? throw new ArgumentNullException(nameof(name));
+
+            var metadata = new StyledPropertyMetadata<TValue>(
+                defaultValue,
+                defaultBindingMode: defaultBindingMode,
+                coerce: coerce);
+
+            var result = new StyledProperty<TValue>(
+                name,
+                typeof(TOwner),
+                metadata,
+                inherits,
+                validate);
+            AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
+            return result;
+        }
+        
+        /// <inheritdoc cref="Register{TOwner, TValue}" />
+        /// <param name="notifying">
+        /// A method that gets called before and after the property starts being notified on an
+        /// object; the bool argument will be true before and false afterwards. This callback is
+        /// intended to support IsDataContextChanging.
+        /// </param>
+        internal static StyledProperty<TValue> Register<TOwner, TValue>(
+            string name,
+            TValue defaultValue,
+            bool inherits,
+            BindingMode defaultBindingMode,
+            Func<TValue, bool>? validate,
+            Func<AvaloniaObject, TValue, TValue>? coerce,
+            Action<AvaloniaObject, bool>? notifying)
                 where TOwner : AvaloniaObject
         {
             _ = name ?? throw new ArgumentNullException(nameof(name));
@@ -496,6 +523,13 @@ namespace Avalonia
             object? value,
             BindingPriority priority);
 
+        /// <summary>
+        /// Routes an untyped SetCurrentValue call to a typed call.
+        /// </summary>
+        /// <param name="o">The object instance.</param>
+        /// <param name="value">The value.</param>
+        internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value);
+
         /// <summary>
         /// Routes an untyped Bind call to a typed call.
         /// </summary>

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

@@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters
         {
             if (value == null)
             {
-                return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null;
+                return null;
             }
 
             if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)

+ 0 - 3
src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs

@@ -1,6 +1,3 @@
-using System;
-using Avalonia.Data;
-
 namespace Avalonia.Diagnostics
 {
     /// <summary>

+ 12 - 11
src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs

@@ -3,28 +3,23 @@ using Avalonia.Data;
 namespace Avalonia.Diagnostics
 {
     /// <summary>
-    /// Holds diagnostic-related information about the value of a <see cref="AvaloniaProperty"/>
-    /// on a <see cref="AvaloniaObject"/>.
+    /// Holds diagnostic-related information about the value of an <see cref="AvaloniaProperty"/>
+    /// on an <see cref="AvaloniaObject"/>.
     /// </summary>
     public class AvaloniaPropertyValue
     {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="AvaloniaPropertyValue"/> class.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="value">The current property value.</param>
-        /// <param name="priority">The priority of the current value.</param>
-        /// <param name="diagnostic">A diagnostic string.</param>
-        public AvaloniaPropertyValue(
+        internal AvaloniaPropertyValue(
             AvaloniaProperty property,
             object? value,
             BindingPriority priority,
-            string? diagnostic)
+            string? diagnostic,
+            bool isOverriddenCurrentValue)
         {
             Property = property;
             Value = value;
             Priority = priority;
             Diagnostic = diagnostic;
+            IsOverriddenCurrentValue = isOverriddenCurrentValue;
         }
 
         /// <summary>
@@ -46,5 +41,11 @@ namespace Avalonia.Diagnostics
         /// Gets a diagnostic string.
         /// </summary>
         public string? Diagnostic { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to 
+        /// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
+        /// </summary>
+        public bool IsOverriddenCurrentValue { get; }
     }
 }

+ 5 - 0
src/Avalonia.Base/DirectPropertyBase.cs

@@ -152,6 +152,11 @@ namespace Avalonia
             return null;
         }
 
+        internal override void RouteSetCurrentValue(AvaloniaObject o, object? value)
+        {
+            RouteSetValue(o, value, BindingPriority.LocalValue);
+        }
+
         /// <summary>
         /// Routes an untyped Bind call to a typed call.
         /// </summary>

+ 5 - 13
src/Avalonia.Base/Input/DragEventArgs.cs

@@ -1,36 +1,28 @@
 using System;
 using Avalonia.Interactivity;
 using Avalonia.Metadata;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Input
 {
     public class DragEventArgs : RoutedEventArgs
     {
-        private Interactive _target;
-        private Point _targetLocation;
+        private readonly Interactive _target;
+        private readonly Point _targetLocation;
 
         public DragDropEffects DragEffects { get; set; }
 
-        public IDataObject Data { get; private set; }
+        public IDataObject Data { get; }
 
-        public KeyModifiers KeyModifiers { get; private set; }
+        public KeyModifiers KeyModifiers { get; }
 
         public Point GetPosition(Visual relativeTo)
         {
-            var point = new Point(0, 0);
-
             if (relativeTo == null)
             {
                 throw new ArgumentNullException(nameof(relativeTo));
             }
 
-            if (_target != null)
-            {
-                point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point;
-            }
-
-            return point;
+            return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
         }
 
         [Unstable]

+ 1 - 1
src/Avalonia.Base/Input/KeyGesture.cs

@@ -136,7 +136,7 @@ namespace Avalonia.Input
             return StringBuilderCache.GetStringAndRelease(s);
         }
 
-        public bool Matches(KeyEventArgs keyEvent) =>
+        public bool Matches(KeyEventArgs? keyEvent) =>
             keyEvent != null &&
             keyEvent.KeyModifiers == KeyModifiers &&
             ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key);

+ 19 - 25
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
-using System.Linq;
 using Avalonia.Input.Navigation;
 using Avalonia.VisualTree;
 
@@ -51,7 +50,7 @@ namespace Avalonia.Input
 
             // If there's a custom keyboard navigation handler as an ancestor, use that.
             var custom = (element as Visual)?.FindAncestorOfType<ICustomKeyboardNavigation>(true);
-            if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce))
+            if (custom is not null && HandlePreCustomNavigation(custom, element, direction, out var ce))
                 return ce;
 
             var result = direction switch
@@ -117,32 +116,27 @@ namespace Avalonia.Input
             NavigationDirection direction,
             [NotNullWhen(true)] out IInputElement? result)
         {
-            if (customHandler != null)
+            var (handled, next) = customHandler.GetNext(element, direction);
+
+            if (handled)
             {
-                var (handled, next) = customHandler.GetNext(element, direction);
+                if (next is not null)
+                {
+                    result = next;
+                    return true;
+                }
 
-                if (handled)
+                var r = direction switch
                 {
-                    if (next != null)
-                    {
-                        result = next;
-                        return true;
-                    }
-                    else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
-                    {
-                        var r = direction switch
-                        {
-                            NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
-                            NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
-                            _ => throw new NotSupportedException(),
-                        };
-
-                        if (r is object)
-                        {
-                            result = r;
-                            return true;
-                        }
-                    }
+                    NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
+                    NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
+                    _ => null
+                };
+
+                if (r is not null)
+                {
+                    result = r;
+                    return true;
                 }
             }
 

+ 15 - 33
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Input.Navigation
@@ -54,8 +52,7 @@ namespace Avalonia.Input.Navigation
                 // Avoid the endless loop here for Cycle groups
                 if (loopStartElement == nextTabElement)
                     break;
-                if (loopStartElement == null)
-                    loopStartElement = nextTabElement;
+                loopStartElement ??= nextTabElement;
 
                 var firstTabElementInside = GetNextTab(null, nextTabElement, true);
                 if (firstTabElementInside != null)
@@ -80,12 +77,9 @@ namespace Avalonia.Input.Navigation
 
         public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e)
         {
-            if (e is IInputElement container)
+            if (e is IInputElement container && GetLastInTree(container) is { } last)
             {
-                var last = GetLastInTree(container);
-
-                if (last is object)
-                    return GetNextTab(last, false);
+                return GetNextTab(last, false);
             }
 
             return null;
@@ -93,11 +87,8 @@ namespace Avalonia.Input.Navigation
 
         public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly)
         {
-            if (e is null && container is null)
-                throw new InvalidOperationException("Either 'e' or 'container' must be non-null.");
-
-            if (container is null)
-                container = GetGroupParent(e!);
+            container ??=
+                GetGroupParent(e ?? throw new InvalidOperationException("Either 'e' or 'container' must be non-null."));
 
             KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container);
 
@@ -163,8 +154,7 @@ namespace Avalonia.Input.Navigation
                 // Avoid the endless loop here
                 if (loopStartElement == nextTabElement)
                     break;
-                if (loopStartElement == null)
-                    loopStartElement = nextTabElement;
+                loopStartElement ??= nextTabElement;
 
                 // At this point nextTabElement is TabGroup
                 var lastTabElementInside = GetPrevTab(null, nextTabElement, true);
@@ -189,22 +179,18 @@ namespace Avalonia.Input.Navigation
 
         public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e)
         {
-            if (e is IInputElement container)
+            if (e is IInputElement container && GetFirstChild(container) is { } first)
             {
-                var first = GetFirstChild(container);
-
-                if (first is object)
-                    return GetPrevTab(first, null, false);
+                return GetPrevTab(first, null, false);
             }
 
             return null;
         }
 
-        private static IInputElement? FocusedElement(IInputElement e)
+        private static IInputElement? FocusedElement(IInputElement? e)
         {
-            var iie = e;
             // Focus delegation is enabled only if keyboard focus is outside the container
-            if (iie != null && !iie.IsKeyboardFocusWithin)
+            if (e != null && !e.IsKeyboardFocusWithin)
             {
                 var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
                 if (focusedElement != null)
@@ -229,13 +215,11 @@ namespace Avalonia.Input.Navigation
         private static IInputElement? GetFirstChild(IInputElement e)
         {
             // If the element has a FocusedElement it should be its first child
-            if (FocusedElement(e) is IInputElement focusedElement)
+            if (FocusedElement(e) is { } focusedElement)
                 return focusedElement;
 
             // Return the first visible element.
-            var uiElement = e as InputElement;
-
-            if (uiElement is null || IsVisibleAndEnabled(uiElement))
+            if (e is not InputElement uiElement || IsVisibleAndEnabled(uiElement))
             {
                 if (e is Visual elementAsVisual)
                 {
@@ -265,7 +249,7 @@ namespace Avalonia.Input.Navigation
         private static IInputElement? GetLastChild(IInputElement e)
         {
             // If the element has a FocusedElement it should be its last child
-            if (FocusedElement(e) is IInputElement focusedElement)
+            if (FocusedElement(e) is { } focusedElement)
                 return focusedElement;
 
             // Return the last visible element.
@@ -273,9 +257,7 @@ namespace Avalonia.Input.Navigation
 
             if (uiElement == null || IsVisibleAndEnabled(uiElement))
             {
-                var elementAsVisual = e as Visual;
-
-                if (elementAsVisual != null)
+                if (e is Visual elementAsVisual)
                 {
                     var children = elementAsVisual.VisualChildren;
                     var count = children.Count;
@@ -322,7 +304,7 @@ namespace Avalonia.Input.Navigation
             return firstTabElement;
         }
 
-        private static IInputElement? GetLastInTree(IInputElement container)
+        private static IInputElement GetLastInTree(IInputElement container)
         {
             IInputElement? result;
             IInputElement? c = container;

+ 3 - 3
src/Avalonia.Base/Input/Platform/IClipboard.cs

@@ -6,9 +6,9 @@ namespace Avalonia.Input.Platform
     [NotClientImplementable]
     public interface IClipboard
     {
-        Task<string> GetTextAsync();
+        Task<string?> GetTextAsync();
 
-        Task SetTextAsync(string text);
+        Task SetTextAsync(string? text);
 
         Task ClearAsync();
 
@@ -16,6 +16,6 @@ namespace Avalonia.Input.Platform
         
         Task<string[]> GetFormatsAsync();
         
-        Task<object> GetDataAsync(string format);
+        Task<object?> GetDataAsync(string format);
     }
 }

+ 23 - 0
src/Avalonia.Base/Input/TextInput/ITextEditable.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
+
+namespace Avalonia.Input.TextInput
+{
+    [NotClientImplementable]
+    public interface ITextEditable
+    {
+        event EventHandler TextChanged;
+        event EventHandler SelectionChanged;
+        event EventHandler CompositionChanged;
+        int SelectionStart { get; set; }
+        int SelectionEnd { get; set; }
+        int CompositionStart { get; }
+        int CompositionEnd { get; }
+        
+        string? Text { get; set; }
+    }
+}

+ 11 - 0
src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs

@@ -1,4 +1,5 @@
 using System;
+using Avalonia.Media.TextFormatting;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Input.TextInput
@@ -30,6 +31,11 @@ namespace Avalonia.Input.TextInput
         /// </summary>
         void SetPreeditText(string? text);
 
+        /// <summary>
+        /// Sets the current composing region. This doesn't remove the composing text from the commited text.
+        /// </summary>
+        void SetComposingRegion(TextRange? region);
+
         /// <summary>
         /// Indicates if text input client is capable of providing the text around the cursor
         /// </summary>
@@ -43,6 +49,11 @@ namespace Avalonia.Input.TextInput
         /// </summary>
         event EventHandler? SurroundingTextChanged;
 
+        /// <summary>
+        /// Gets or sets a platform editable. Text and selection changes made in the editable are forwarded to the IM client.
+        /// </summary>
+        ITextEditable? TextEditable { get; set; }
+
         void SelectInSurroundingText(int start, int end);
     }
 

+ 3 - 3
src/Avalonia.Base/LogicalTree/LogicalExtensions.cs

@@ -48,7 +48,7 @@ namespace Avalonia.LogicalTree
         /// <param name="logical">The logical.</param>
         /// <param name="includeSelf">If given logical should be included in search.</param>
         /// <returns>First ancestor of given type.</returns>
-        public static T? FindLogicalAncestorOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
+        public static T? FindLogicalAncestorOfType<T>(this ILogical? logical, bool includeSelf = false) where T : class
         {
             if (logical is null)
             {
@@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree
         /// <param name="logical">The logical.</param>
         /// <param name="includeSelf">If given logical should be included in search.</param>
         /// <returns>First descendant of given type.</returns>
-        public static T? FindLogicalDescendantOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
+        public static T? FindLogicalDescendantOfType<T>(this ILogical? logical, bool includeSelf = false) where T : class
         {
             if (logical is null)
             {
@@ -185,7 +185,7 @@ namespace Avalonia.LogicalTree
         /// True if <paramref name="logical"/> is an ancestor of <paramref name="target"/>;
         /// otherwise false.
         /// </returns>
-        public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target)
+        public static bool IsLogicalAncestorOf(this ILogical? logical, ILogical? target)
         {
             var current = target?.LogicalParent;
 

+ 3 - 8
src/Avalonia.Base/Media/Color.cs

@@ -147,16 +147,11 @@ namespace Avalonia.Media
         /// <param name="s">The color string.</param>
         /// <param name="color">The parsed color</param>
         /// <returns>The status of the operation.</returns>
-        public static bool TryParse(string s, out Color color)
+        public static bool TryParse(string? s, out Color color)
         {
             color = default;
 
-            if (s is null)
-            {
-                return false;
-            }
-
-            if (s.Length == 0)
+            if (string.IsNullOrEmpty(s))
             {
                 return false;
             }
@@ -336,7 +331,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Parses the given string representing a CSS color value into a new <see cref="Color"/>.
         /// </summary>
-        private static bool TryParseCssFormat(string s, out Color color)
+        private static bool TryParseCssFormat(string? s, out Color color)
         {
             bool prefixMatched = false;
 

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

@@ -240,7 +240,7 @@ namespace Avalonia.Media
         /// </summary>
         /// <param name="foreground">The foreground brush.</param>
         /// <param name="glyphRun">The glyph run.</param>
-        public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
+        public void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun)
         {
             _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));
 

+ 13 - 13
src/Avalonia.Base/Media/DrawingGroup.cs

@@ -13,14 +13,14 @@ namespace Avalonia.Media
         public static readonly StyledProperty<double> OpacityProperty =
             AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1);
 
-        public static readonly StyledProperty<Transform> TransformProperty =
-            AvaloniaProperty.Register<DrawingGroup, Transform>(nameof(Transform));
+        public static readonly StyledProperty<Transform?> TransformProperty =
+            AvaloniaProperty.Register<DrawingGroup, Transform?>(nameof(Transform));
 
-        public static readonly StyledProperty<Geometry> ClipGeometryProperty =
-            AvaloniaProperty.Register<DrawingGroup, Geometry>(nameof(ClipGeometry));
+        public static readonly StyledProperty<Geometry?> ClipGeometryProperty =
+            AvaloniaProperty.Register<DrawingGroup, Geometry?>(nameof(ClipGeometry));
 
-        public static readonly StyledProperty<IBrush> OpacityMaskProperty =
-            AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask));
+        public static readonly StyledProperty<IBrush?> OpacityMaskProperty =
+            AvaloniaProperty.Register<DrawingGroup, IBrush?>(nameof(OpacityMask));
 
         public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty =
             AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>(
@@ -36,19 +36,19 @@ namespace Avalonia.Media
             set => SetValue(OpacityProperty, value);
         }
 
-        public Transform Transform
+        public Transform? Transform
         {
             get => GetValue(TransformProperty);
             set => SetValue(TransformProperty, value);
         }
 
-        public Geometry ClipGeometry
+        public Geometry? ClipGeometry
         {
             get => GetValue(ClipGeometryProperty);
             set => SetValue(ClipGeometryProperty, value);
         }
 
-        public IBrush OpacityMask
+        public IBrush? OpacityMask
         {
             get => GetValue(OpacityMaskProperty);
             set => SetValue(OpacityMaskProperty, value);
@@ -159,7 +159,7 @@ namespace Avalonia.Media
 
             public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
             {
-                if (((brush == null) && (pen == null)) || (geometry == null))
+                if ((brush == null) && (pen == null))
                 {
                     return;
                 }
@@ -167,9 +167,9 @@ namespace Avalonia.Media
                 AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
             }
 
-            public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
+            public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
             {
-                if (foreground == null || glyphRun == null)
+                if (foreground == null)
                 {
                     return;
                 }
@@ -184,7 +184,7 @@ namespace Avalonia.Media
                 AddDrawing(glyphRunDrawing);
             }
 
-            public void DrawLine(IPen pen, Point p1, Point p2)
+            public void DrawLine(IPen? pen, Point p1, Point p2)
             {
                 if (pen == null)
                 {

+ 3 - 3
src/Avalonia.Base/Media/DrawingImage.cs

@@ -20,8 +20,8 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="Drawing"/> property.
         /// </summary>
-        public static readonly StyledProperty<Drawing> DrawingProperty =
-            AvaloniaProperty.Register<DrawingImage, Drawing>(nameof(Drawing));
+        public static readonly StyledProperty<Drawing?> DrawingProperty =
+            AvaloniaProperty.Register<DrawingImage, Drawing?>(nameof(Drawing));
 
         /// <inheritdoc/>
         public event EventHandler? Invalidated;
@@ -30,7 +30,7 @@ namespace Avalonia.Media
         /// Gets or sets the drawing content.
         /// </summary>
         [Content]
-        public Drawing Drawing
+        public Drawing? Drawing
         {
             get => GetValue(DrawingProperty);
             set => SetValue(DrawingProperty, value);

+ 2 - 2
src/Avalonia.Base/Media/FontFamily.cs

@@ -119,7 +119,7 @@ namespace Avalonia.Media
 
                 case 2:
                     {
-                        var source = segments[0].StartsWith("/")
+                        var source = segments[0].StartsWith("/", StringComparison.Ordinal)
                             ? new Uri(segments[0], UriKind.Relative)
                             : new Uri(segments[0], UriKind.RelativeOrAbsolute);
 
@@ -188,7 +188,7 @@ namespace Avalonia.Media
         {
             unchecked
             {
-                return ((FamilyNames != null ? FamilyNames.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0);
+                return (FamilyNames.GetHashCode() * 397) ^ (Key is not null ? Key.GetHashCode() : 0);
             }
         }
 

+ 1 - 4
src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs

@@ -41,10 +41,7 @@ namespace Avalonia.Media.Fonts
             {
                 var hash = (int)2166136261;
 
-                if (Source != null)
-                {
-                    hash = (hash * 16777619) ^ Source.GetHashCode();
-                }
+                hash = (hash * 16777619) ^ Source.GetHashCode();
 
                 if (BaseUri != null)
                 {

+ 3 - 3
src/Avalonia.Base/Media/FormattedText.cs

@@ -1354,7 +1354,7 @@ namespace Avalonia.Media
                     {
                         var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0);
 
-                        if (highlightBounds != null)
+                        if (highlightBounds.Count > 0)
                         {
                             foreach (var bound in highlightBounds)
                             {
@@ -1365,7 +1365,7 @@ namespace Avalonia.Media
                                     // Convert logical units (which extend leftward from the right edge
                                     // of the paragraph) to physical units.
                                     //
-                                    // Note that since rect is in logical units, rect.Right corresponds to 
+                                    // Note that since rect is in logical units, rect.Right corresponds to
                                     // the visual *left* edge of the rectangle in the RTL case. Specifically,
                                     // is the distance leftward from the right edge of the formatting rectangle
                                     // whose width is the paragraph width passed to FormatLine.
@@ -1384,7 +1384,7 @@ namespace Avalonia.Media
                                 else
                                 {
                                     accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union);
-                                }                                  
+                                }
                             }
                         }
                     }

+ 3 - 3
src/Avalonia.Base/Media/GeometryDrawing.cs

@@ -15,8 +15,8 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="Geometry"/> property.
         /// </summary>
-        public static readonly StyledProperty<Geometry> GeometryProperty =
-            AvaloniaProperty.Register<GeometryDrawing, Geometry>(nameof(Geometry));
+        public static readonly StyledProperty<Geometry?> GeometryProperty =
+            AvaloniaProperty.Register<GeometryDrawing, Geometry?>(nameof(Geometry));
 
         /// <summary>
         /// Defines the <see cref="Brush"/> property.
@@ -34,7 +34,7 @@ namespace Avalonia.Media
         /// Gets or sets the <see cref="Avalonia.Media.Geometry"/> that describes the shape of this <see cref="GeometryDrawing"/>.
         /// </summary>
         [Content]
-        public Geometry Geometry
+        public Geometry? Geometry
         {
             get => GetValue(GeometryProperty);
             set => SetValue(GeometryProperty, value);

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

@@ -2,19 +2,19 @@
 {
     public class GlyphRunDrawing : Drawing
     {
-        public static readonly StyledProperty<IBrush> ForegroundProperty =
-            AvaloniaProperty.Register<GlyphRunDrawing, IBrush>(nameof(Foreground));
+        public static readonly StyledProperty<IBrush?> ForegroundProperty =
+            AvaloniaProperty.Register<GlyphRunDrawing, IBrush?>(nameof(Foreground));
 
-        public static readonly StyledProperty<GlyphRun> GlyphRunProperty =
-            AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun>(nameof(GlyphRun));
+        public static readonly StyledProperty<GlyphRun?> GlyphRunProperty =
+            AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun?>(nameof(GlyphRun));
 
-        public IBrush Foreground
+        public IBrush? Foreground
         {
             get => GetValue(ForegroundProperty);
             set => SetValue(ForegroundProperty, value);
         }
 
-        public GlyphRun GlyphRun
+        public GlyphRun? GlyphRun
         {
             get => GetValue(GlyphRunProperty);
             set => SetValue(GlyphRunProperty, value);

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

@@ -254,7 +254,7 @@ namespace Avalonia.Media
         /// <param name="s">The HSL color string to parse.</param>
         /// <param name="hslColor">The parsed <see cref="HslColor"/>.</param>
         /// <returns>True if parsing was successful; otherwise, false.</returns>
-        public static bool TryParse(string s, out HslColor hslColor)
+        public static bool TryParse(string? s, out HslColor hslColor)
         {
             bool prefixMatched = false;
 

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

@@ -254,7 +254,7 @@ namespace Avalonia.Media
         /// <param name="s">The HSV color string to parse.</param>
         /// <param name="hsvColor">The parsed <see cref="HsvColor"/>.</param>
         /// <returns>True if parsing was successful; otherwise, false.</returns>
-        public static bool TryParse(string s, out HsvColor hsvColor)
+        public static bool TryParse(string? s, out HsvColor hsvColor)
         {
             bool prefixMatched = false;
 

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

@@ -1,5 +1,4 @@
 using Avalonia.Metadata;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Media
 {
@@ -12,6 +11,6 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets the visual to draw.
         /// </summary>
-        Visual Visual { get; }
+        Visual? Visual { get; }
     }
 }

+ 6 - 18
src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs

@@ -39,17 +39,8 @@ namespace Avalonia.Media.Immutable
             {
                 return true;
             }
-            else if (other is null)
-            {
-                return false;
-            }
 
-            if (Offset != other.Offset)
-            {
-                return false;
-            }
-
-            return SequenceEqual(Dashes, other.Dashes);
+            return other is not null && Offset == other.Offset && SequenceEqual(_dashes, other.Dashes);
         }
 
         /// <inheritdoc/>
@@ -58,30 +49,27 @@ namespace Avalonia.Media.Immutable
             var hashCode = 717868523;
             hashCode = hashCode * -1521134295 + Offset.GetHashCode();
 
-            if (_dashes != null)
+            foreach (var i in _dashes)
             {
-                foreach (var i in _dashes)
-                {
-                    hashCode = hashCode * -1521134295 + i.GetHashCode();
-                }
+                hashCode = hashCode * -1521134295 + i.GetHashCode();
             }
 
             return hashCode;
         }
 
-        private static bool SequenceEqual(IReadOnlyList<double> left, IReadOnlyList<double>? right)
+        private static bool SequenceEqual(double[] left, IReadOnlyList<double>? right)
         {
             if (ReferenceEquals(left, right))
             {
                 return true;
             }
 
-            if (left == null || right == null || left.Count != right.Count)
+            if (right is null || left.Length != right.Count)
             {
                 return false;
             }
 
-            for (var c = 0; c < left.Count; c++)
+            for (var c = 0; c < left.Length; c++)
             {
                 if (left[c] != right[c])
                 {

+ 3 - 4
src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs

@@ -1,5 +1,4 @@
 using Avalonia.Media.Imaging;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Media.Immutable
 {
@@ -31,11 +30,11 @@ namespace Avalonia.Media.Immutable
             RelativeRect? destinationRect = null,
             double opacity = 1,
             ImmutableTransform? transform = null,
-            RelativePoint transformOrigin = new RelativePoint(),
+            RelativePoint transformOrigin = default,
             RelativeRect? sourceRect = null,
             Stretch stretch = Stretch.Uniform,
             TileMode tileMode = TileMode.None,
-            Imaging.BitmapInterpolationMode bitmapInterpolationMode = Imaging.BitmapInterpolationMode.Default)
+            BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
             : base(
                   alignmentX,
                   alignmentY,
@@ -62,6 +61,6 @@ namespace Avalonia.Media.Immutable
         }
 
         /// <inheritdoc/>
-        public Visual Visual { get; }
+        public Visual? Visual { get; }
     }
 }

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

@@ -22,8 +22,8 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="Stroke"/> property.
         /// </summary>
-        public static readonly StyledProperty<IBrush> StrokeProperty =
-            AvaloniaProperty.Register<TextDecoration, IBrush>(nameof(Stroke));
+        public static readonly StyledProperty<IBrush?> StrokeProperty =
+            AvaloniaProperty.Register<TextDecoration, IBrush?>(nameof(Stroke));
 
         /// <summary>
         /// Defines the <see cref="StrokeThicknessUnit"/> property.
@@ -34,8 +34,8 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="StrokeDashArray"/> property.
         /// </summary>
-        public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty =
-            AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>>(nameof(StrokeDashArray));
+        public static readonly StyledProperty<AvaloniaList<double>?> StrokeDashArrayProperty =
+            AvaloniaProperty.Register<TextDecoration, AvaloniaList<double>?>(nameof(StrokeDashArray));
 
         /// <summary>
         /// Defines the <see cref="StrokeDashOffset"/> property.
@@ -82,7 +82,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets or sets the <see cref="IBrush"/> that specifies how the <see cref="TextDecoration"/> is painted.
         /// </summary>
-        public IBrush Stroke
+        public IBrush? Stroke
         {
             get { return GetValue(StrokeProperty); }
             set { SetValue(StrokeProperty, value); }
@@ -101,7 +101,7 @@ namespace Avalonia.Media
         /// Gets or sets a collection of <see cref="double"/> values that indicate the pattern of dashes and gaps
         /// that is used to draw the <see cref="TextDecoration"/>.
         /// </summary>
-        public AvaloniaList<double> StrokeDashArray
+        public AvaloniaList<double>? StrokeDashArray
         {
             get { return GetValue(StrokeDashArrayProperty); }
             set { SetValue(StrokeDashArrayProperty, value); }
@@ -220,7 +220,7 @@ namespace Avalonia.Media
 
                 var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY));
 
-                if (intersections != null && intersections.Count > 0)
+                if (intersections.Count > 0)
                 {
                     var last = baselineOrigin.X;
                     var finalPos = last + glyphRun.Size.Width;

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

@@ -118,14 +118,17 @@ namespace Avalonia.Media.TextFormatting
                 fontManager.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
                     defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo,
                     out var fallbackTypeface);
-
-            var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface);
-
-            if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count))
+                        
+            if (matchFound)
             {
-                //Fallback found
-                return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface),
-                    biDiLevel);
+                // 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);
+                }                
             }
 
             // no fallback found

+ 3 - 4
src/Avalonia.Base/Media/VisualBrush.cs

@@ -1,5 +1,4 @@
 using Avalonia.Media.Immutable;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Media
 {
@@ -11,8 +10,8 @@ namespace Avalonia.Media
         /// <summary>
         /// Defines the <see cref="Visual"/> property.
         /// </summary>
-        public static readonly StyledProperty<Visual> VisualProperty =
-            AvaloniaProperty.Register<VisualBrush, Visual>(nameof(Visual));
+        public static readonly StyledProperty<Visual?> VisualProperty =
+            AvaloniaProperty.Register<VisualBrush, Visual?>(nameof(Visual));
 
         static VisualBrush()
         {
@@ -38,7 +37,7 @@ namespace Avalonia.Media
         /// <summary>
         /// Gets or sets the visual to draw.
         /// </summary>
-        public Visual Visual
+        public Visual? Visual
         {
             get { return GetValue(VisualProperty); }
             set { SetValue(VisualProperty, value); }

+ 2 - 2
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@@ -49,7 +49,7 @@ namespace Avalonia.Platform
         /// <param name="pen">The stroke pen.</param>
         /// <param name="p1">The first point of the line.</param>
         /// <param name="p2">The second point of the line.</param>
-        void DrawLine(IPen pen, Point p1, Point p2);
+        void DrawLine(IPen? pen, Point p1, Point p2);
 
         /// <summary>
         /// Draws a geometry.
@@ -91,7 +91,7 @@ namespace Avalonia.Platform
         /// </summary>
         /// <param name="foreground">The foreground.</param>
         /// <param name="glyphRun">The glyph run.</param>
-        void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun);
+        void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun);
 
         /// <summary>
         /// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer

+ 12 - 0
src/Avalonia.Base/Platform/IPlatformBehaviorInhibition.cs

@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+
+namespace Avalonia.Platform
+{
+    /// <summary>
+    /// Allows to inhibit platform specific behavior.
+    /// </summary>
+    public interface IPlatformBehaviorInhibition
+    {
+        Task SetInhibitAppSleep(bool inhibitAppSleep, string reason);
+    }
+}

+ 1 - 1
src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs

@@ -26,6 +26,6 @@ namespace Avalonia.Platform
 
         bool CurrentThreadIsLoopThread { get; }
 
-        event Action<DispatcherPriority?> Signaled;
+        event Action<DispatcherPriority?>? Signaled;
     }
 }

+ 15 - 18
src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -18,26 +19,21 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
 {
     public AssemblyDescriptor(Assembly assembly)
     {
-        Assembly = assembly;
+        Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
+        Resources = assembly.GetManifestResourceNames()
+            .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
+        Name = assembly.GetName().Name;
 
-        if (assembly != null)
+        using var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName);
+        if (resources != null)
         {
-            Resources = assembly.GetManifestResourceNames()
-                .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
-            Name = assembly.GetName().Name;
-            using (var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName))
-            {
-                if (resources != null)
-                {
-                    Resources.Remove(Constants.AvaloniaResourceName);
+            Resources.Remove(Constants.AvaloniaResourceName);
 
-                    var indexLength = new BinaryReader(resources).ReadInt32();
-                    var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
-                    var baseOffset = indexLength + 4;
-                    AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor)
-                        new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
-                }
-            }
+            var indexLength = new BinaryReader(resources).ReadInt32();
+            var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
+            var baseOffset = indexLength + 4;
+            AvaloniaResources = index.ToDictionary(GetPathRooted, r => (IAssetDescriptor)
+                new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
         }
     }
 
@@ -45,6 +41,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
     public Dictionary<string, IAssetDescriptor>? Resources { get; }
     public Dictionary<string, IAssetDescriptor>? AvaloniaResources { get; }
     public string? Name { get; }
+
     private static string GetPathRooted(AvaloniaResourcesIndexEntry r) =>
         r.Path![0] == '/' ? r.Path : '/' + r.Path;
 }

+ 2 - 2
src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs

@@ -7,9 +7,9 @@ namespace Avalonia.Platform.Storage;
 /// </summary>
 public sealed class FilePickerFileType
 {
-    public FilePickerFileType(string name)
+    public FilePickerFileType(string? name)
     {
-        Name = name;
+        Name = name ?? string.Empty;
     }
 
     /// <summary>

+ 23 - 24
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore
         private IDisposable? _subscription;
         private bool _hasValue;
         private TValue? _value;
+        private TValue? _defaultValue;
+        private bool _isDefaultValueInitialized;
 
         protected BindingEntryBase(
             ValueFrame frame,
@@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore
 
         protected abstract BindingValue<TValue> ConvertAndValidate(TSource value);
         protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value);
+        protected abstract TValue GetDefaultValue(Type ownerType);
 
         protected virtual void Start(bool produceValue)
         {
@@ -104,17 +107,6 @@ namespace Avalonia.PropertyStore
             };
         }
 
-        private void ClearValue()
-        {
-            if (_hasValue)
-            {
-                _hasValue = false;
-                _value = default;
-                if (_subscription is not null)
-                    Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
-            }
-        }
-
         private void SetValue(BindingValue<TValue> value)
         {
             static void Execute(BindingEntryBase<TValue, TSource> instance, BindingValue<TValue> value)
@@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore
 
                 LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value);
 
-                if (value.HasValue)
-                {
-                    if (!instance._hasValue || !EqualityComparer<TValue>.Default.Equals(instance._value, value.Value))
-                    {
-                        instance._value = value.Value;
-                        instance._hasValue = true;
-                        if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
-                            instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
-                    }
-                }
-                else if (value.Type != BindingValueType.DoNothing)
+                var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue();
+
+                if (!instance._hasValue || !EqualityComparer<TValue>.Default.Equals(instance._value, effectiveValue))
                 {
-                    instance.ClearValue();
+                    instance._value = effectiveValue;
+                    instance._hasValue = true;
                     if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
-                        instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority);
+                        instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
                 }
             }
 
+            if (value.Type == BindingValueType.DoNothing)
+                return;
+
             if (Dispatcher.UIThread.CheckAccess())
             {
                 Execute(this, value);
@@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore
             _subscription = null;
             Frame.OnBindingCompleted(this);
         }
+
+        private TValue GetCachedDefaultValue()
+        {
+            if (!_isDefaultValueInitialized)
+            {
+                _defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType());
+                _isDefaultValueInitialized = true;
+            }
+
+            return _defaultValue!;
+        }
     }
 }

+ 6 - 0
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@@ -29,6 +29,12 @@ namespace Avalonia.PropertyStore
         /// </summary>
         public BindingPriority BasePriority { get; protected set; }
 
+        /// <summary>
+        /// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to 
+        /// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
+        /// </summary>
+        public bool IsOverridenCurrentValue { get; set; }
+
         /// <summary>
         /// Begins a reevaluation pass on the effective value.
         /// </summary>

+ 18 - 11
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@@ -57,7 +57,7 @@ namespace Avalonia.PropertyStore
             Debug.Assert(priority != BindingPriority.LocalValue);
             UpdateValueEntry(value, priority);
 
-            SetAndRaiseCore(owner,  (StyledProperty<T>)value.Property, GetValue(value), priority);
+            SetAndRaiseCore(owner,  (StyledProperty<T>)value.Property, GetValue(value), priority, false);
         }
 
         public void SetLocalValueAndRaise(
@@ -65,7 +65,16 @@ namespace Avalonia.PropertyStore
             StyledProperty<T> property,
             T value)
         {
-            SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
+            SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue, false);
+        }
+
+        public void SetCurrentValueAndRaise(
+            ValueStore owner,
+            StyledProperty<T> property,
+            T value)
+        {
+            IsOverridenCurrentValue = true;
+            SetAndRaiseCore(owner, property, value, Priority, true);
         }
 
         public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
@@ -98,7 +107,7 @@ namespace Avalonia.PropertyStore
             Debug.Assert(Priority != BindingPriority.Animation);
             Debug.Assert(BasePriority != BindingPriority.Unset);
             UpdateValueEntry(null, BindingPriority.Animation);
-            SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority);
+            SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority, false);
         }
 
         public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
@@ -158,15 +167,16 @@ namespace Avalonia.PropertyStore
             ValueStore owner,
             StyledProperty<T> property,
             T value,
-            BindingPriority priority)
+            BindingPriority priority,
+            bool isOverriddenCurrentValue)
         {
-            Debug.Assert(priority < BindingPriority.Inherited);
-
             var oldValue = Value;
             var valueChanged = false;
             var baseValueChanged = false;
             var v = value;
 
+            IsOverridenCurrentValue = isOverriddenCurrentValue;
+
             if (_uncommon?._coerce is { } coerce)
                 v = coerce(owner.Owner, value);
 
@@ -209,7 +219,6 @@ namespace Avalonia.PropertyStore
             T baseValue,
             BindingPriority basePriority)
         {
-            Debug.Assert(priority < BindingPriority.Inherited);
             Debug.Assert(basePriority > BindingPriority.Animation);
             Debug.Assert(priority <= basePriority);
 
@@ -225,7 +234,7 @@ namespace Avalonia.PropertyStore
                 bv = coerce(owner.Owner, baseValue);
             }
 
-            if (priority != BindingPriority.Unset && !EqualityComparer<T>.Default.Equals(Value, v))
+            if (!EqualityComparer<T>.Default.Equals(Value, v))
             {
                 Value = v;
                 valueChanged = true;
@@ -233,9 +242,7 @@ namespace Avalonia.PropertyStore
                     _uncommon._uncoercedValue = value;
             }
 
-            if (priority != BindingPriority.Unset &&
-                (BasePriority == BindingPriority.Unset ||
-                 !EqualityComparer<T>.Default.Equals(_baseValue, bv)))
+            if (!EqualityComparer<T>.Default.Equals(_baseValue, bv))
             {
                 _baseValue = v;
                 baseValueChanged = true;

+ 37 - 12
src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs

@@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore
     {
         private readonly ValueStore _owner;
         private IDisposable? _subscription;
+        private T? _defaultValue;
+        private bool _isDefaultValueInitialized;
 
         public LocalValueBindingObserver(ValueStore owner, StyledProperty<T> property)
         {
@@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore
 
         public void OnNext(T value)
         {
-            static void Execute(ValueStore owner, StyledProperty<T> property, T value)
+            static void Execute(LocalValueBindingObserver<T> instance, T value)
             {
-                if (property.ValidateValue?.Invoke(value) != false)
-                    owner.SetValue(property, value, BindingPriority.LocalValue);
-                else
-                    owner.ClearLocalValue(property);
+                var owner = instance._owner;
+                var property = instance.Property;
+
+                if (property.ValidateValue?.Invoke(value) == false)
+                    value = instance.GetCachedDefaultValue();
+
+                owner.SetValue(property, value, BindingPriority.LocalValue);
             }
 
             if (Dispatcher.UIThread.CheckAccess())
             {
-                Execute(_owner, Property, value);
+                Execute(this, value);
             }
             else
             {
                 // To avoid allocating closure in the outer scope we need to capture variables
                 // locally. This allows us to skip most of the allocations when on UI thread.
-                var instance = _owner;
-                var property = Property;
+                var instance = this;
                 var newValue = value;
-                Dispatcher.UIThread.Post(() => Execute(instance, property, newValue));
+                Dispatcher.UIThread.Post(() => Execute(instance, newValue));
             }
         }
 
@@ -74,11 +78,21 @@ namespace Avalonia.PropertyStore
                 LoggingUtils.LogIfNecessary(owner.Owner, property, value);
 
                 if (value.HasValue)
-                    owner.SetValue(property, value.Value, BindingPriority.LocalValue);
-                else if (value.Type != BindingValueType.DataValidationError)
-                    owner.ClearLocalValue(property);
+                {
+                    var effectiveValue = value.Value;
+                    if (property.ValidateValue?.Invoke(effectiveValue) == false)
+                        effectiveValue = instance.GetCachedDefaultValue();
+                    owner.SetValue(property, effectiveValue, BindingPriority.LocalValue);
+                }
+                else
+                {
+                    owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
+                }
             }
 
+            if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError)
+                return;
+
             if (Dispatcher.UIThread.CheckAccess())
             {
                 Execute(this, value);
@@ -92,5 +106,16 @@ namespace Avalonia.PropertyStore
                 Dispatcher.UIThread.Post(() => Execute(instance, newValue));
             }
         }
+
+        private T GetCachedDefaultValue()
+        {
+            if (!_isDefaultValueInitialized)
+            {
+                _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
+                _isDefaultValueInitialized = true;
+            }
+
+            return _defaultValue!;
+        }
     }
 }

+ 18 - 7
src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Security.Cryptography;
 using Avalonia.Data;
 using Avalonia.Threading;
 
@@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore
     {
         private readonly ValueStore _owner;
         private IDisposable? _subscription;
+        private T? _defaultValue;
+        private bool _isDefaultValueInitialized;
 
         public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty<T> property)
         {
@@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore
 
                 if (value == AvaloniaProperty.UnsetValue)
                 {
-                    owner.ClearLocalValue(property);
-                }
-                else if (value == BindingOperations.DoNothing)
-                {
-                    // Do nothing!
+                    owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
                 }
                 else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue))
                 {
@@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore
                 }
                 else
                 {
-                    owner.ClearLocalValue(property);
+                    owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
                     LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value);
                 }
             }
 
+            if (value == BindingOperations.DoNothing)
+                return;
+
             if (Dispatcher.UIThread.CheckAccess())
             {
                 Execute(this, value);
@@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore
                 Dispatcher.UIThread.Post(() => Execute(instance, newValue));
             }
         }
+
+        private T GetCachedDefaultValue()
+        {
+            if (!_isDefaultValueInitialized)
+            {
+                _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
+                _isDefaultValueInitialized = true;
+            }
+
+            return _defaultValue!;
+        }
     }
 }

+ 2 - 0
src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs

@@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore
         {
             throw new NotSupportedException();
         }
+
+        protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
     }
 }

+ 2 - 0
src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs

@@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore
             
             return value;
         }
+
+        protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
     }
 }

+ 5 - 0
src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs

@@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore
         {
             throw new NotSupportedException();
         }
+
+        protected override object? GetDefaultValue(Type ownerType)
+        {
+            return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue;
+        }
     }
 }

+ 23 - 28
src/Avalonia.Base/PropertyStore/ValueStore.cs

@@ -7,7 +7,6 @@ using Avalonia.Data;
 using Avalonia.Diagnostics;
 using Avalonia.Styling;
 using Avalonia.Utilities;
-using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
 
 namespace Avalonia.PropertyStore
 {
@@ -156,11 +155,12 @@ namespace Avalonia.PropertyStore
             return observer;
         }
 
-        public void ClearLocalValue(AvaloniaProperty property)
+        public void ClearValue(AvaloniaProperty property)
         {
             if (TryGetEffectiveValue(property, out var effective) &&
-                effective.Priority == BindingPriority.LocalValue)
+                (effective.Priority == BindingPriority.LocalValue || effective.IsOverridenCurrentValue))
             {
+                effective.IsOverridenCurrentValue = false;
                 ReevaluateEffectiveValue(property, effective, ignoreLocalValue: true);
             }
         }
@@ -209,6 +209,20 @@ namespace Avalonia.PropertyStore
             }
         }
 
+        public void SetCurrentValue<T>(StyledProperty<T> property, T value)
+        {
+            if (TryGetEffectiveValue(property, out var v))
+            {
+                ((EffectiveValue<T>)v).SetCurrentValueAndRaise(this, property, value);
+            }
+            else
+            {
+                var effectiveValue = new EffectiveValue<T>(Owner, property);
+                AddEffectiveValue(property, effectiveValue);
+                effectiveValue.SetCurrentValueAndRaise(this, property, value);
+            }
+        }
+
         public object? GetValue(AvaloniaProperty property)
         {
             if (_effectiveValues.TryGetValue(property, out var v))
@@ -235,12 +249,7 @@ namespace Avalonia.PropertyStore
             return false;
         }
 
-        public bool IsSet(AvaloniaProperty property)
-        {
-            if (_effectiveValues.TryGetValue(property, out var v))
-                return v.Priority < BindingPriority.Inherited;
-            return false;
-        }
+        public bool IsSet(AvaloniaProperty property) => _effectiveValues.TryGetValue(property, out _);
 
         public void CoerceValue(AvaloniaProperty property)
         {
@@ -380,23 +389,6 @@ namespace Avalonia.PropertyStore
             }
         }
 
-        /// <summary>
-        /// Called by non-LocalValue binding entries to re-evaluate the effective value when the
-        /// binding produces an unset value.
-        /// </summary>
-        /// <param name="property">The bound property.</param>
-        /// <param name="priority">The priority of binding which produced a new value.</param>
-        public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority)
-        {
-            Debug.Assert(priority != BindingPriority.LocalValue);
-
-            if (TryGetEffectiveValue(property, out var existing))
-            {
-                if (priority <= existing.Priority)
-                    ReevaluateEffectiveValue(property, existing);
-            }
-        }
-
         /// <summary>
         /// Called by a <see cref="ValueFrame"/> when its <see cref="ValueFrame.IsActive"/>
         /// state changes.
@@ -507,7 +499,7 @@ namespace Avalonia.PropertyStore
                 if (existing == observer)
                 {
                     _localValueBindings?.Remove(property.Id);
-                    ClearLocalValue(property);
+                    ClearValue(property);
                 }
             }
         }
@@ -633,11 +625,13 @@ namespace Avalonia.PropertyStore
         {
             object? value;
             BindingPriority priority;
+            bool overridden = false;
 
             if (_effectiveValues.TryGetValue(property, out var v))
             {
                 value = v.Value;
                 priority = v.Priority;
+                overridden = v.IsOverridenCurrentValue;
             }
             else if (property.Inherits && TryGetInheritedValue(property, out v))
             {
@@ -654,7 +648,8 @@ namespace Avalonia.PropertyStore
                 property,
                 value,
                 priority,
-                null);
+                null,
+                overridden);
         }
 
         private int InsertFrame(ValueFrame frame)

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
     public abstract class CompositionAnimation : CompositionObject,  ICompositionAnimationBase
     {
         private readonly CompositionPropertySet _propertySet;
-        internal CompositionAnimation(Compositor compositor) : base(compositor, null!)
+        internal CompositionAnimation(Compositor compositor) : base(compositor, null)
         {
             _propertySet = new CompositionPropertySet(compositor);
         }

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Rendering.Composition.Animations
         public void Remove(CompositionAnimation value) => Animations.Remove(value);
         public void RemoveAll() => Animations.Clear();
 
-        public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!)
+        public CompositionAnimationGroup(Compositor compositor) : base(compositor, null)
         {
         }
     }

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations
     {
         private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>();
         private IDictionary<string, ICompositionAnimationBase> _innerface;
-        internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!)
+        internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null)
         {
             _innerface = _inner;
         }

+ 58 - 24
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@@ -20,15 +20,17 @@ public class CompositingRenderer : IRendererWithCompositor
 {
     private readonly IRenderRoot _root;
     private readonly Compositor _compositor;
-    CompositionDrawingContext _recorder = new();
-    DrawingContext _recordingContext;
-    private HashSet<Visual> _dirty = new();
-    private HashSet<Visual> _recalculateChildren = new();
+    private readonly CompositionDrawingContext _recorder = new();
+    private readonly DrawingContext _recordingContext;
+    private readonly HashSet<Visual> _dirty = new();
+    private readonly HashSet<Visual> _recalculateChildren = new();
+    private readonly Action _update;
+
     private bool _queuedUpdate;
-    private Action _update;
     private bool _updating;
+    private bool _isDisposed;
 
-    internal CompositionTarget CompositionTarget;
+    internal CompositionTarget CompositionTarget { get; }
     
     /// <summary>
     /// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered.
@@ -38,6 +40,17 @@ public class CompositingRenderer : IRendererWithCompositor
     /// <inheritdoc/>
     public RendererDiagnostics Diagnostics { get; }
 
+    /// <inheritdoc />
+    public Compositor Compositor => _compositor;
+
+    /// <summary>
+    /// Initializes a new instance of <see cref="CompositingRenderer"/>
+    /// </summary>
+    /// <param name="root">The render root using this renderer.</param>
+    /// <param name="compositor">The associated compositors.</param>
+    /// <param name="surfaces">
+    /// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems.
+    /// </param>
     public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces)
     {
         _root = root;
@@ -66,7 +79,7 @@ public class CompositingRenderer : IRendererWithCompositor
     /// <inheritdoc/>
     public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
 
-    void QueueUpdate()
+    private void QueueUpdate()
     {
         if(_queuedUpdate)
             return;
@@ -77,9 +90,11 @@ public class CompositingRenderer : IRendererWithCompositor
     /// <inheritdoc/>
     public void AddDirty(Visual visual)
     {
+        if (_isDisposed)
+            return;
         if (_updating)
             throw new InvalidOperationException("Visual was invalidated during the render pass");
-        _dirty.Add((Visual)visual);
+        _dirty.Add(visual);
         QueueUpdate();
     }
 
@@ -126,9 +141,11 @@ public class CompositingRenderer : IRendererWithCompositor
     /// <inheritdoc/>
     public void RecalculateChildren(Visual visual)
     {
+        if (_isDisposed)
+            return;
         if (_updating)
             throw new InvalidOperationException("Visual was invalidated during the render pass");
-        _recalculateChildren.Add((Visual)visual);
+        _recalculateChildren.Add(visual);
         QueueUpdate();
     }
 
@@ -171,7 +188,7 @@ public class CompositingRenderer : IRendererWithCompositor
             if (sortedChildren != null)
                 for (var c = 0; c < visualChildren.Count; c++)
                 {
-                    if (!ReferenceEquals(compositionChildren[c], ((Visual)sortedChildren[c].visual).CompositionVisual))
+                    if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual))
                     {
                         mismatch = true;
                         break;
@@ -179,7 +196,7 @@ public class CompositingRenderer : IRendererWithCompositor
                 }
             else
                 for (var c = 0; c < visualChildren.Count; c++)
-                    if (!ReferenceEquals(compositionChildren[c], ((Visual)visualChildren[c]).CompositionVisual))
+                    if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual))
                     {
                         mismatch = true;
                         break;
@@ -201,7 +218,7 @@ public class CompositingRenderer : IRendererWithCompositor
         {
             foreach (var ch in sortedChildren)
             {
-                var compositionChild = ((Visual)ch.visual).CompositionVisual;
+                var compositionChild = ch.visual.CompositionVisual;
                 if (compositionChild != null)
                     compositionChildren.Add(compositionChild);
             }
@@ -210,7 +227,7 @@ public class CompositingRenderer : IRendererWithCompositor
         else
             foreach (var ch in v.GetVisualChildren())
             {
-                var compositionChild = ((Visual)ch).CompositionVisual;
+                var compositionChild = ch.CompositionVisual;
                 if (compositionChild != null)
                     compositionChildren.Add(compositionChild);
             }
@@ -289,13 +306,18 @@ public class CompositingRenderer : IRendererWithCompositor
             _updating = false;
         }
     }
-    
+
+    /// <inheritdoc />
     public void Resized(Size size)
     {
     }
 
+    /// <inheritdoc />
     public void Paint(Rect rect)
     {
+        if (_isDisposed)
+            return;
+
         QueueUpdate();
         CompositionTarget.RequestRedraw();
         if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground)
@@ -304,17 +326,34 @@ public class CompositingRenderer : IRendererWithCompositor
             CompositionTarget.ImmediateUIThreadRender();
     }
 
-    public void Start() => CompositionTarget.IsEnabled = true;
-
-    public void Stop()
+    /// <inheritdoc />
+    public void Start()
     {
-        CompositionTarget.IsEnabled = false;
+        if (_isDisposed)
+            return;
+
+        CompositionTarget.IsEnabled = true;
     }
 
-    public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) => Compositor.TryGetRenderInterfaceFeature(featureType);
+    /// <inheritdoc />
+    public void Stop()
+        => CompositionTarget.IsEnabled = false;
+
+    /// <inheritdoc />
+    public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
+        => Compositor.TryGetRenderInterfaceFeature(featureType);
 
+    /// <inheritdoc />
     public void Dispose()
     {
+        if (_isDisposed)
+            return;
+
+        _isDisposed = true;
+        _dirty.Clear();
+        _recalculateChildren.Clear();
+        SceneInvalidated = null;
+
         Stop();
         CompositionTarget.Dispose();
         
@@ -323,9 +362,4 @@ public class CompositingRenderer : IRendererWithCompositor
         if (Compositor.Loop.RunsInBackground)
             _compositor.Commit().Wait();
     }
-
-    /// <summary>
-    /// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
-    /// </summary>
-    public Compositor Compositor => _compositor;
 }

+ 1 - 2
src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs

@@ -1,4 +1,3 @@
-using System;
 using System.Threading.Tasks;
 using Avalonia.Rendering.Composition.Server;
 using Avalonia.Threading;
@@ -7,7 +6,7 @@ namespace Avalonia.Rendering.Composition;
 
 public class CompositionDrawingSurface : CompositionSurface
 {
-    internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server;
+    internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server!;
     internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server))
     {
     }

+ 2 - 2
src/Avalonia.Base/Rendering/Composition/CompositionObject.cs

@@ -22,7 +22,7 @@ namespace Avalonia.Rendering.Composition
         public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
 
         private protected InlineDictionary<CompositionProperty, IAnimationInstance> PendingAnimations;
-        internal CompositionObject(Compositor compositor, ServerObject server)
+        internal CompositionObject(Compositor compositor, ServerObject? server)
         {
             Compositor = compositor;
             Server = server;
@@ -32,7 +32,7 @@ namespace Avalonia.Rendering.Composition
         /// The associated Compositor
         /// </summary>
         public Compositor Compositor { get; }
-        internal ServerObject Server { get; }
+        internal ServerObject? Server { get; }
         public bool IsDisposed { get; private set; }
         private bool _registeredForSerialization;
 

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition
         private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>();
         private readonly Dictionary<string, CompositionObject> _objects = new Dictionary<string, CompositionObject>();
         
-        internal CompositionPropertySet(Compositor compositor) : base(compositor, null!)
+        internal CompositionPropertySet(Compositor compositor) : base(compositor, null)
         {
         }
 

+ 12 - 2
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@@ -88,8 +88,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
     }
 
     /// <inheritdoc/>
-    public void DrawLine(IPen pen, Point p1, Point p2)
+    public void DrawLine(IPen? pen, Point p1, Point p2)
     {
+        if (pen is null)
+        {
+            return;
+        }
+
         var next = NextDrawAs<LineNode>();
 
         if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
@@ -159,8 +164,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
     public object? GetFeature(Type t) => null;
 
     /// <inheritdoc/>
-    public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
+    public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
     {
+        if (foreground is null)
+        {
+            return;
+        }
+
         var next = NextDrawAs<GlyphRunNode>();
 
         if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))

+ 0 - 2
src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs

@@ -165,8 +165,6 @@ namespace Avalonia.Rendering.Composition.Expressions
         
         public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
         {
-            if (context.ForeignFunctionInterface == null)
-                return default;
             var args = new List<ExpressionVariant>();
             foreach (var expr in Parameters)
                 args.Add(expr.Evaluate(ref context));

+ 0 - 1
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
-using Avalonia.Rendering.Composition.Server;
 
 // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
 

+ 2 - 2
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@@ -66,7 +66,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
         _impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect);
     }
 
-    public void DrawLine(IPen pen, Point p1, Point p2)
+    public void DrawLine(IPen? pen, Point p1, Point p2)
     {
         _impl.DrawLine(pen, p1, p2);
     }
@@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
         _impl.DrawEllipse(brush, pen, rect);
     }
 
-    public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
+    public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
     {
         _impl.DrawGlyphRun(foreground, glyphRun);
     }

+ 2 - 3
src/Avalonia.Base/Rendering/DefaultRenderTimer.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Diagnostics;
-using System.Threading.Tasks;
 using Avalonia.Platform;
 
 namespace Avalonia.Rendering
@@ -59,7 +57,8 @@ namespace Avalonia.Rendering
             }
         }
 
-        public bool RunsInBackground => true;
+        /// <inheritdoc />
+        public virtual bool RunsInBackground => true;
 
         /// <summary>
         /// Starts the timer.

+ 4 - 1
src/Avalonia.Base/Rendering/IRenderLoop.cs

@@ -27,7 +27,10 @@ namespace Avalonia.Rendering
         /// </summary>
         /// <param name="i">The update task.</param>
         void Remove(IRenderLoopTask i);
-        
+
+        /// <summary>
+        /// Indicates if the rendering is done on a non-UI thread.
+        /// </summary>
         bool RunsInBackground { get; }
     }
 }

+ 0 - 2
src/Avalonia.Base/Rendering/IRenderRoot.cs

@@ -1,6 +1,4 @@
 using Avalonia.Metadata;
-using Avalonia.Platform;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering
 {

+ 0 - 1
src/Avalonia.Base/Rendering/IRenderTimer.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Threading.Tasks;
 using Avalonia.Metadata;
 
 namespace Avalonia.Rendering

+ 3 - 0
src/Avalonia.Base/Rendering/IRenderer.cs

@@ -90,6 +90,9 @@ namespace Avalonia.Rendering
     
     public interface IRendererWithCompositor : IRenderer
     {
+        /// <summary>
+        /// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
+        /// </summary>
         Compositor Compositor { get; }
     }
 }

+ 4 - 2
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@@ -48,8 +48,10 @@ namespace Avalonia.Rendering
         /// <inheritdoc/>
         void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
         {
-            var visual = brush.Visual;
-            Render(new DrawingContext(context), visual, visual.Bounds);
+            if (brush.Visual is { } visual)
+            {
+                Render(new DrawingContext(context), visual, visual.Bounds);
+            }
         }
 
         internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds)

+ 1 - 0
src/Avalonia.Base/Rendering/RenderLoop.cs

@@ -87,6 +87,7 @@ namespace Avalonia.Rendering
             }
         }
 
+        /// <inheritdoc />
         public bool RunsInBackground => Timer.RunsInBackground;
 
         private void TimerTick(TimeSpan time)

+ 2 - 5
src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs

@@ -80,11 +80,8 @@ namespace Avalonia.Rendering.SceneGraph
             {
                 p *= Transform.Invert();
 
-                if (Material != null)
-                {
-                    var rect = Rect.Rect;
-                    return rect.ContainsExclusive(p);
-                }
+                var rect = Rect.Rect;
+                return rect.ContainsExclusive(p);
             }
 
             return false;

+ 8 - 1
src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs

@@ -8,13 +8,20 @@ namespace Avalonia.Rendering
     /// <summary>
     /// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms 
     /// </summary>
-    
     public class UiThreadRenderTimer : DefaultRenderTimer
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UiThreadRenderTimer"/> class.
+        /// </summary>
+        /// <param name="framesPerSecond">The number of frames per second at which the loop should run.</param>
         public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond)
         {
         }
 
+        /// <inheritdoc />
+        public override bool RunsInBackground => false;
+
+        /// <inheritdoc />
         protected override IDisposable StartCore(Action<TimeSpan> tick)
         {
             bool cancelled = false;

+ 4 - 0
src/Avalonia.Base/StyledElement.cs

@@ -41,7 +41,11 @@ namespace Avalonia
         public static readonly StyledProperty<object?> DataContextProperty =
             AvaloniaProperty.Register<StyledElement, object?>(
                 nameof(DataContext),
+                defaultValue: null,
                 inherits: true,
+                defaultBindingMode: BindingMode.OneWay,
+                validate: null,
+                coerce: null,
                 notifying: DataContextNotifying);
 
         /// <summary>

+ 30 - 14
src/Avalonia.Base/StyledProperty.cs

@@ -194,24 +194,48 @@ namespace Avalonia
         }
 
         /// <inheritdoc/>
-        [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
         internal override IDisposable? RouteSetValue(
             AvaloniaObject target,
             object? value,
             BindingPriority priority)
+        {
+            if (ShouldSetValue(target, value, out var converted))
+                return target.SetValue<TValue>(this, converted, priority);
+            return null;
+        }
+
+        internal override void RouteSetCurrentValue(AvaloniaObject target, object? value)
+        {
+            if (ShouldSetValue(target, value, out var converted))
+                target.SetCurrentValue<TValue>(this, converted);
+        }
+
+        internal override IDisposable RouteBind(
+            AvaloniaObject target,
+            IObservable<object?> source,
+            BindingPriority priority)
+        {
+            return target.Bind<TValue>(this, source, priority);
+        }
+
+        [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
+        private bool ShouldSetValue(AvaloniaObject target, object? value, [NotNullWhen(true)] out TValue? converted)
         {
             if (value == BindingOperations.DoNothing)
             {
-                return null;
+                converted = default;
+                return false; 
             }
-            else if (value == UnsetValue)
+            if (value == UnsetValue)
             {
                 target.ClearValue(this);
-                return null;
+                converted = default;
+                return false;
             }
-            else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted))
+            else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var v))
             {
-                return target.SetValue<TValue>(this, (TValue)converted!, priority);
+                converted = (TValue)v!;
+                return true;
             }
             else
             {
@@ -220,14 +244,6 @@ namespace Avalonia
             }
         }
 
-        internal override IDisposable RouteBind(
-            AvaloniaObject target,
-            IObservable<object?> source,
-            BindingPriority priority)
-        {
-            return target.Bind<TValue>(this, source, priority);
-        }
-
         private object? GetDefaultBoxedValue(Type type)
         {
             _ = type ?? throw new ArgumentNullException(nameof(type));

+ 3 - 8
src/Avalonia.Base/Utilities/TypeUtilities.cs

@@ -212,7 +212,7 @@ namespace Avalonia.Utilities
 
             var toTypeConverter = TypeDescriptor.GetConverter(toUnderl);
 
-            if (toTypeConverter.CanConvertFrom(from) == true)
+            if (toTypeConverter.CanConvertFrom(from))
             {
                 result = toTypeConverter.ConvertFrom(null, culture, value);
                 return true;
@@ -220,7 +220,7 @@ namespace Avalonia.Utilities
 
             var fromTypeConverter = TypeDescriptor.GetConverter(from);
 
-            if (fromTypeConverter.CanConvertTo(toUnderl) == true)
+            if (fromTypeConverter.CanConvertTo(toUnderl))
             {
                 result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl);
                 return true;
@@ -329,7 +329,7 @@ namespace Avalonia.Utilities
         }
 
         [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
-        public static T ConvertImplicit<T>(object value)
+        public static T ConvertImplicit<T>(object? value)
         {
             if (TryConvertImplicit(typeof(T), value, out var result))
             {
@@ -369,11 +369,6 @@ namespace Avalonia.Utilities
         /// </remarks>
         public static bool IsNumeric(Type type)
         {
-            if (type == null)
-            {
-                return false;
-            }
-
             var underlyingType = Nullable.GetUnderlyingType(type);
 
             if (underlyingType != null)

+ 7 - 61
src/Avalonia.Base/Utilities/WeakEvent.cs

@@ -1,8 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Reflection;
 using System.Runtime.CompilerServices;
 using Avalonia.Threading;
 
@@ -15,7 +11,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
 {
     private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe;
 
-    readonly ConditionalWeakTable<object, Subscription> _subscriptions = new();
+    private readonly ConditionalWeakTable<object, Subscription> _subscriptions = new();
 
     internal WeakEvent(
         Action<TSender, EventHandler<TEventArgs>> subscribe,
@@ -51,56 +47,6 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
         private readonly WeakEvent<TSender, TEventArgs> _ev;
         private readonly TSender _target;
         private readonly Action _compact;
-
-        struct Entry
-        {
-            WeakReference<IWeakEventSubscriber<TEventArgs>>? _reference;
-            int _hashCode;
-
-            public Entry(IWeakEventSubscriber<TEventArgs> r)
-            {
-                if (r == null)
-                {
-                    _reference = null;
-                    _hashCode = 0;
-                    return;
-                }
-
-                _hashCode = r.GetHashCode();
-                _reference = new WeakReference<IWeakEventSubscriber<TEventArgs>>(r);
-            }
-
-            public bool IsEmpty
-            {
-                get
-                {
-                    if (_reference == null)
-                        return true;
-                    if (_reference.TryGetTarget(out _))
-                        return false;
-                    _reference = null;
-                    return true;
-                }
-            }
-
-            public bool TryGetTarget([MaybeNullWhen(false)]out IWeakEventSubscriber<TEventArgs> target)
-            {
-                if (_reference == null)
-                {
-                    target = null!;
-                    return false;
-                }
-                return _reference.TryGetTarget(out target);
-            }
-
-            public bool Equals(IWeakEventSubscriber<TEventArgs> r)
-            {
-                if (_reference == null || r.GetHashCode() != _hashCode)
-                    return false;
-                return _reference.TryGetTarget(out var target) && target == r;
-            }
-        }
-
         private readonly Action _unsubscribe;
         private readonly WeakHashList<IWeakEventSubscriber<TEventArgs>> _list = new();
         private bool _compactScheduled;
@@ -114,7 +60,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
             _unsubscribe = ev._subscribe(target, OnEvent);
         }
 
-        void Destroy()
+        private void Destroy()
         {
             if(_destroyed)
                 return;
@@ -134,15 +80,15 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
                 ScheduleCompact();
         }
 
-        void ScheduleCompact()
+        private void ScheduleCompact()
         {
             if(_compactScheduled || _destroyed)
                 return;
             _compactScheduled = true;
             Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background);
         }
-        
-        void Compact()
+
+        private void Compact()
         {
             if(!_compactScheduled)
                 return;
@@ -152,7 +98,7 @@ public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : Event
                 Destroy();
         }
 
-        void OnEvent(object? sender, TEventArgs eventArgs)
+        private void OnEvent(object? sender, TEventArgs eventArgs)
         {
             var alive = _list.GetAlive();
             if(alive == null)
@@ -196,4 +142,4 @@ public class WeakEvent
             return () => unsubscribe(s, handler);
         });
     }
-}
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio