浏览代码

Merge branch 'master' into feature/gpu-external-memory

Nikita Tsukanov 2 年之前
父节点
当前提交
2b4cd42196
共有 100 个文件被更改,包括 796 次插入447 次删除
  1. 1 0
      Directory.Build.props
  2. 37 9
      NOTICE.md
  3. 1 0
      build/Base.props
  4. 0 1
      build/SharedVersion.props
  5. 1 1
      global.json
  6. 4 0
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  7. 4 1
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  8. 113 0
      native/Avalonia.Native/src/OSX/PlatformSettings.mm
  9. 3 1
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  10. 19 1
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  11. 4 2
      native/Avalonia.Native/src/OSX/WindowImpl.h
  12. 11 7
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  13. 1 0
      native/Avalonia.Native/src/OSX/common.h
  14. 10 0
      native/Avalonia.Native/src/OSX/main.mm
  15. 20 5
      packages/Avalonia/AvaloniaBuildTasks.targets
  16. 1 1
      samples/ControlCatalog.Desktop/Program.cs
  17. 72 1
      samples/ControlCatalog/MainView.xaml.cs
  18. 3 3
      samples/ControlCatalog/Models/StateData.cs
  19. 2 2
      samples/ControlCatalog/Pages/CarouselPage.xaml
  20. 1 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  21. 32 32
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  22. 0 1
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  23. 1 1
      samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
  24. 1 1
      samples/ControlCatalog/Pages/PointerCanvas.cs
  25. 0 1
      samples/ControlCatalog/Pages/PointerContactsTab.cs
  26. 29 29
      samples/ControlCatalog/Pages/ScreenPage.cs
  27. 1 1
      samples/ControlCatalog/Pages/TabControlPage.xaml
  28. 1 1
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  29. 1 1
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  30. 0 1
      samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
  31. 2 6
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  32. 1 1
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  33. 0 1
      samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
  34. 1 1
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  35. 0 2
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  36. 1 2
      samples/ControlCatalog/ViewModels/NotificationViewModel.cs
  37. 0 1
      samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs
  38. 1 1
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  39. 0 1
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  40. 2 3
      samples/Directory.Build.props
  41. 3 1
      samples/MiniMvvm/MiniMvvm.csproj
  42. 8 6
      samples/MiniMvvm/PropertyChangedExtensions.cs
  43. 0 1
      samples/MiniMvvm/ViewModelBase.cs
  44. 1 0
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  45. 2 1
      samples/Previewer/Previewer.csproj
  46. 0 1
      samples/ReactiveUIDemo/ReactiveUIDemo.csproj
  47. 1 3
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  48. 1 4
      samples/VirtualizationDemo/MainWindow.xaml
  49. 0 10
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  50. 0 2
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  51. 1 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  52. 1 1
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  53. 12 7
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  54. 16 0
      src/Android/Avalonia.Android/AvaloniaView.cs
  55. 1 1
      src/Android/Avalonia.Android/ChoreographerTimer.cs
  56. 14 0
      src/Android/Avalonia.Android/IAndroidNavigationService.cs
  57. 94 0
      src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs
  58. 28 0
      src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
  59. 12 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  60. 1 2
      src/Avalonia.Base/Animation/Animation.cs
  61. 1 3
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  62. 1 1
      src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
  63. 0 2
      src/Avalonia.Base/Animation/Animators/Animator`1.cs
  64. 1 1
      src/Avalonia.Base/Animation/Animators/ColorAnimator.cs
  65. 1 1
      src/Avalonia.Base/Animation/Animators/TransformAnimator.cs
  66. 1 0
      src/Avalonia.Base/Animation/Clock.cs
  67. 2 2
      src/Avalonia.Base/Animation/CrossFade.cs
  68. 0 2
      src/Avalonia.Base/Animation/PageSlide.cs
  69. 0 2
      src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
  70. 11 1
      src/Avalonia.Base/Avalonia.Base.csproj
  71. 1 1
      src/Avalonia.Base/AvaloniaObject.cs
  72. 55 121
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  73. 3 3
      src/Avalonia.Base/AvaloniaProperty`1.cs
  74. 1 0
      src/Avalonia.Base/ClassBindingManager.cs
  75. 1 1
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  76. 0 1
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  77. 1 1
      src/Avalonia.Base/Controls/NameScopeLocator.cs
  78. 5 6
      src/Avalonia.Base/Data/BindingOperations.cs
  79. 1 1
      src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs
  80. 0 1
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  81. 2 4
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  82. 8 9
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  83. 38 23
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  84. 24 41
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  85. 2 3
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  86. 1 1
      src/Avalonia.Base/Data/Core/StreamNode.cs
  87. 7 2
      src/Avalonia.Base/Data/IndexerBinding.cs
  88. 2 3
      src/Avalonia.Base/Data/IndexerDescriptor.cs
  89. 20 30
      src/Avalonia.Base/Data/InstancedBinding.cs
  90. 1 2
      src/Avalonia.Base/Input/Cursor.cs
  91. 8 1
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  92. 1 0
      src/Avalonia.Base/Input/Gestures.cs
  93. 1 0
      src/Avalonia.Base/Input/InputElement.cs
  94. 4 4
      src/Avalonia.Base/Input/InputManager.cs
  95. 1 0
      src/Avalonia.Base/Input/MouseDevice.cs
  96. 5 0
      src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs
  97. 4 0
      src/Avalonia.Base/Input/ScrollGestureEventArgs.cs
  98. 1 1
      src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
  99. 1 4
      src/Avalonia.Base/Interactivity/EventRoute.cs
  100. 1 2
      src/Avalonia.Base/Interactivity/InteractiveExtensions.cs

+ 1 - 0
Directory.Build.props

@@ -7,5 +7,6 @@
       <AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
       <AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
       <MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
       <MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
       <RunApiCompat>False</RunApiCompat>
       <RunApiCompat>False</RunApiCompat>
+      <LangVersion>11</LangVersion>
   </PropertyGroup>
   </PropertyGroup>
 </Project>
 </Project>

+ 37 - 9
NOTICE.md

@@ -81,14 +81,14 @@ A "contributor" is any person that distributes its contribution under this licen
 
 
 https://github.com/wayland-project/wayland-protocols
 https://github.com/wayland-project/wayland-protocols
 
 
-Copyright © 2008-2013 Kristian Høgsberg
-Copyright © 2010-2013 Intel Corporation
-Copyright © 2013      Rafael Antognolli
-Copyright © 2013      Jasper St. Pierre
-Copyright © 2014      Jonas Ådahl
-Copyright © 2014      Jason Ekstrand
-Copyright © 2014-2015 Collabora, Ltd.
-Copyright © 2015      Red Hat Inc.
+Copyright © 2008-2013 Kristian Høgsberg
+Copyright © 2010-2013 Intel Corporation
+Copyright © 2013      Rafael Antognolli
+Copyright © 2013      Jasper St. Pierre
+Copyright © 2014      Jonas Ådahl
+Copyright © 2014      Jason Ekstrand
+Copyright © 2014-2015 Collabora, Ltd.
+Copyright © 2015      Red Hat Inc.
 
 
 Permission is hereby granted, free of charge, to any person obtaining a
 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the "Software"),
 copy of this software and associated documentation files (the "Software"),
@@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 https://github.com/toptensoftware/RichTextKit
 https://github.com/toptensoftware/RichTextKit
 
 
-Copyright © 2019 Topten Software. All Rights Reserved.
+Copyright © 2019 Topten Software. All Rights Reserved.
 
 
 Licensed under the Apache License, Version 2.0 (the "License"); you may 
 Licensed under the Apache License, Version 2.0 (the "License"); you may 
 not use this product except in compliance with the License. You may obtain 
 not use this product except in compliance with the License. You may obtain 
@@ -334,3 +334,31 @@ https://github.com/flutter/flutter
 //ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 //ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Reactive Extensions
+
+https://github.com/dotnet/reactive
+
+The MIT License (MIT)
+
+Copyright (c) .NET Foundation and Contributors
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 1 - 0
build/Base.props

@@ -2,6 +2,7 @@
   <ItemGroup Condition="'$(TargetFramework)' != 'net6'">
   <ItemGroup Condition="'$(TargetFramework)' != 'net6'">
     <PackageReference Include="System.ValueTuple" Version="4.5.0" />
     <PackageReference Include="System.ValueTuple" Version="4.5.0" />
     <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
     <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
+    <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
     <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
     <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 0 - 1
build/SharedVersion.props

@@ -8,7 +8,6 @@
     <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
     <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
-    <LangVersion>preview</LangVersion>
     <PackageLicenseExpression>MIT</PackageLicenseExpression>
     <PackageLicenseExpression>MIT</PackageLicenseExpression>
     <PackageIcon>Icon.png</PackageIcon>
     <PackageIcon>Icon.png</PackageIcon>
     <PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription>
     <PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription>

+ 1 - 1
global.json

@@ -4,6 +4,6 @@
         "rollForward": "latestFeature"
         "rollForward": "latestFeature"
     },
     },
     "msbuild-sdks": {
     "msbuild-sdks": {
-        "Microsoft.Build.Traversal": "1.0.43"
+        "Microsoft.Build.Traversal": "3.2.0"
     }
     }
 }
 }

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

@@ -50,6 +50,7 @@
 		BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
 		BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
 		BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
 		BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
 		ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
 		ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
+		EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; };
 /* End PBXBuildFile section */
 /* End PBXBuildFile section */
 
 
 /* Begin PBXFileReference section */
 /* Begin PBXFileReference section */
@@ -103,6 +104,7 @@
 		BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
 		BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
 		BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
 		BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
 		ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
 		ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
+		EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 /* End PBXFileReference section */
 
 
 /* Begin PBXFrameworksBuildPhase section */
 /* Begin PBXFrameworksBuildPhase section */
@@ -163,6 +165,7 @@
 				1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
 				1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
 				37A517B22159597E00FBA241 /* Screens.mm */,
 				37A517B22159597E00FBA241 /* Screens.mm */,
 				37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
 				37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
+				EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */,
 				AB7A61F02147C815003C5833 /* Products */,
 				AB7A61F02147C815003C5833 /* Products */,
 				AB661C1C2148230E00291242 /* Frameworks */,
 				AB661C1C2148230E00291242 /* Frameworks */,
 				18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
 				18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
@@ -299,6 +302,7 @@
 				1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
 				1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
 				18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
 				18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
 				18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
 				18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
+				EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};

+ 4 - 1
native/Avalonia.Native/src/OSX/AvnWindow.mm

@@ -44,7 +44,7 @@
 
 
 -(bool) isDialog
 -(bool) isDialog
 {
 {
-    return _parent->IsDialog();
+    return _parent->IsModal();
 }
 }
 
 
 -(double) getExtendedTitleBarHeight
 -(double) getExtendedTitleBarHeight
@@ -281,6 +281,9 @@
 
 
 - (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
 - (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
 {
 {
+    if (_parent == nullptr)
+        return;
+        
     _parent->BringToFront();
     _parent->BringToFront();
     
     
     dispatch_async(dispatch_get_main_queue(), ^{
     dispatch_async(dispatch_get_main_queue(), ^{

+ 113 - 0
native/Avalonia.Native/src/OSX/PlatformSettings.mm

@@ -0,0 +1,113 @@
+#include "common.h"
+
+@interface CocoaThemeObserver : NSObject
+-(id)initWithCallback:(IAvnActionCallback *)callback;
+@end
+
+class PlatformSettings : public ComSingleObject<IAvnPlatformSettings, &IID_IAvnPlatformSettings>
+{
+    CocoaThemeObserver* observer;
+
+public:
+    FORWARD_IUNKNOWN()
+    virtual AvnPlatformThemeVariant GetPlatformTheme() override
+    {
+        @autoreleasepool
+        {
+            if (@available(macOS 10.14, *))
+            {
+                if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua
+                    || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight) {
+                    return AvnPlatformThemeVariant::Light;
+                } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua
+                    || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark) {
+                    return AvnPlatformThemeVariant::Dark;
+                } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua
+                    || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) {
+                    return AvnPlatformThemeVariant::HighContrastLight;
+                } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua
+                    || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) {
+                    return AvnPlatformThemeVariant::HighContrastDark;
+                }
+            }
+            return AvnPlatformThemeVariant::Light;
+        }
+    }
+    
+    virtual unsigned int GetAccentColor() override
+    {
+        @autoreleasepool
+        {
+            if (@available(macOS 10.14, *))
+            {
+                auto color = [NSColor controlAccentColor];
+                return to_argb(color);
+            }
+            else
+            {
+                return 0;
+            }
+        }
+    }
+    
+    virtual void RegisterColorsChange(IAvnActionCallback *callback) override
+    {
+        if (@available(macOS 10.14, *))
+        {
+            observer = [[CocoaThemeObserver alloc] initWithCallback: callback];
+            [[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil];
+        }
+    }
+    
+private:
+    unsigned int to_argb(NSColor* color)
+    {
+        const CGFloat* components = CGColorGetComponents(color.CGColor);
+        unsigned int alpha = static_cast<unsigned int>(CGColorGetAlpha(color.CGColor) * 0xFF);
+        unsigned int red = static_cast<unsigned int>(components[0] * 0xFF);
+        unsigned int green = static_cast<unsigned int>(components[1] * 0xFF);
+        unsigned int blue = static_cast<unsigned int>(components[2] * 0xFF);
+        return (alpha << 24) + (red << 16) + (green << 8) + blue;
+    }
+};
+
+@implementation CocoaThemeObserver
+{
+    ComPtr<IAvnActionCallback> _callback;
+}
+- (id) initWithCallback:(IAvnActionCallback *)callback{
+    self = [super init];
+    if (self) {
+        _callback = callback;
+    }
+    return self;
+}
+
+/*- (void)didChangeValueForKey:(NSString *)key {
+    if([key isEqualToString:@"effectiveAppearance"]) {
+        _callback->Run();
+    }
+    else {
+        [super didChangeValueForKey:key];
+    }
+}*/
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+                      ofObject:(id)object
+                        change:(NSDictionary *)change
+                       context:(void *)context {
+    if([keyPath isEqualToString:@"effectiveAppearance"]) {
+        _callback->Run();
+    } else {
+        [super observeValueForKeyPath:keyPath
+                             ofObject:object
+                               change:change
+                              context:context];
+    }
+}
+@end
+
+extern IAvnPlatformSettings* CreatePlatformSettings()
+{
+    return new PlatformSettings();
+}

+ 3 - 1
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@@ -92,11 +92,13 @@ BEGIN_INTERFACE_MAP()
 
 
     virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override;
     virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override;
 
 
+    virtual HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant variant) override;
+
     virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
     virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
             IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
             IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
             void *sourceHandle) override;
             void *sourceHandle) override;
 
 
-    virtual bool IsDialog();
+    virtual bool IsModal();
 
 
     id<AvnWindowProtocol> GetWindowProtocol ();
     id<AvnWindowProtocol> GetWindowProtocol ();
                            
                            

+ 19 - 1
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@@ -498,6 +498,24 @@ HRESULT WindowBaseImpl::SetTransparencyMode(AvnWindowTransparencyMode mode) {
     return S_OK;
     return S_OK;
 }
 }
 
 
+HRESULT WindowBaseImpl::SetFrameThemeVariant(AvnPlatformThemeVariant variant) {
+    START_COM_CALL;
+
+    NSAppearanceName appearanceName;
+    if (@available(macOS 10.14, *))
+    {
+        appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua;
+    }
+    else
+    {
+        appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameVibrantDark : NSAppearanceNameAqua;
+    }
+
+    [Window setAppearance: [NSAppearance appearanceNamed: appearanceName]];
+
+    return S_OK;
+}
+
 HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) {
 HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) {
     START_COM_CALL;
     START_COM_CALL;
 
 
@@ -542,7 +560,7 @@ HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, Av
     return S_OK;
     return S_OK;
 }
 }
 
 
-bool WindowBaseImpl::IsDialog() {
+bool WindowBaseImpl::IsModal() {
     return false;
     return false;
 }
 }
 
 

+ 4 - 2
native/Avalonia.Native/src/OSX/WindowImpl.h

@@ -23,7 +23,7 @@ private:
     NSRect _preZoomSize;
     NSRect _preZoomSize;
     bool _transitioningWindowState;
     bool _transitioningWindowState;
     bool _isClientAreaExtended;
     bool _isClientAreaExtended;
-    bool _isDialog;
+    bool _isModal;
     WindowImpl* _parent;
     WindowImpl* _parent;
     std::list<WindowImpl*> _children;
     std::list<WindowImpl*> _children;
     AvnExtendClientAreaChromeHints _extendClientHints;
     AvnExtendClientAreaChromeHints _extendClientHints;
@@ -91,7 +91,9 @@ BEGIN_INTERFACE_MAP()
 
 
     virtual HRESULT SetWindowState (AvnWindowState state) override;
     virtual HRESULT SetWindowState (AvnWindowState state) override;
 
 
-    virtual bool IsDialog() override;
+    virtual bool IsModal() override;
+    
+    bool IsOwned();
     
     
     virtual void BringToFront () override;
     virtual void BringToFront () override;
     
     

+ 11 - 7
native/Avalonia.Native/src/OSX/WindowImpl.mm

@@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
     START_COM_CALL;
     START_COM_CALL;
 
 
     @autoreleasepool {
     @autoreleasepool {
-        _isDialog = isDialog || _parent != nullptr;
+        _isModal = isDialog;
 
 
         WindowBaseImpl::Show(activate, isDialog);
         WindowBaseImpl::Show(activate, isDialog);
 
 
@@ -97,7 +97,7 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
         
         
         _parent = cparent;
         _parent = cparent;
 
 
-        _isDialog = _parent != nullptr;
+        _isModal = _parent != nullptr;
         
         
         if(_parent != nullptr && Window != nullptr){
         if(_parent != nullptr && Window != nullptr){
             // If one tries to show a child window with a minimized parent window, then the parent window will be
             // If one tries to show a child window with a minimized parent window, then the parent window will be
@@ -123,7 +123,7 @@ void WindowImpl::BringToFront()
     {
     {
         if ([Window isVisible] && ![Window isMiniaturized])
         if ([Window isVisible] && ![Window isMiniaturized])
         {
         {
-            if(IsDialog())
+            if(IsModal())
             {
             {
                 Activate();
                 Activate();
             }
             }
@@ -150,7 +150,7 @@ bool WindowImpl::CanBecomeKeyWindow()
 {
 {
     for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
     for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
     {
     {
-        if((*iterator)->IsDialog())
+        if((*iterator)->IsModal())
         {
         {
             return false;
             return false;
         }
         }
@@ -569,8 +569,12 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
     }
     }
 }
 }
 
 
-bool WindowImpl::IsDialog() {
-    return _isDialog;
+bool WindowImpl::IsModal() {
+    return _isModal;
+}
+
+bool WindowImpl::IsOwned() {
+    return _parent != nullptr;
 }
 }
 
 
 NSWindowStyleMask WindowImpl::GetStyle() {
 NSWindowStyleMask WindowImpl::GetStyle() {
@@ -599,7 +603,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
             break;
             break;
     }
     }
 
 
-    if (!IsDialog()) {
+    if (!IsOwned()) {
         s |= NSWindowStyleMaskMiniaturizable;
         s |= NSWindowStyleMaskMiniaturizable;
     }
     }
 
 

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

@@ -27,6 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem();
 extern IAvnMenuItem* CreateAppMenuItemSeparator();
 extern IAvnMenuItem* CreateAppMenuItemSeparator();
 extern IAvnApplicationCommands* CreateApplicationCommands();
 extern IAvnApplicationCommands* CreateApplicationCommands();
 extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
 extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
+extern IAvnPlatformSettings* CreatePlatformSettings();
 extern void SetAppMenu(IAvnMenu *menu);
 extern void SetAppMenu(IAvnMenu *menu);
 extern void SetServicesMenu (IAvnMenu* menu);
 extern void SetServicesMenu (IAvnMenu* menu);
 extern IAvnMenu* GetAppMenu ();
 extern IAvnMenu* GetAppMenu ();

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

@@ -398,6 +398,16 @@ public:
         }
         }
     }
     }
     
     
+    virtual HRESULT CreatePlatformSettings (IAvnPlatformSettings** ppv) override
+    {
+        START_COM_CALL;
+        
+        @autoreleasepool
+        {
+            *ppv = ::CreatePlatformSettings();
+            return S_OK;
+        }
+    }
 };
 };
 
 
 extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
 extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

+ 20 - 5
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -48,8 +48,12 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <Target Name="_GenerateAvaloniaResourcesDependencyCache" BeforeTargets="GenerateAvaloniaResources">
   <Target Name="_GenerateAvaloniaResourcesDependencyCache" BeforeTargets="GenerateAvaloniaResources">
+    <PropertyGroup>
+      <_AvaloniaResourcesInputsCacheFilePath>$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache</_AvaloniaResourcesInputsCacheFilePath>
+    </PropertyGroup>
+
     <ItemGroup>
     <ItemGroup>
-      <CustomAdditionalGenerateAvaloniaResourcesInputs Include="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" />
+      <CustomAdditionalGenerateAvaloniaResourcesInputs Include="$(_AvaloniaResourcesInputsCacheFilePath)" />
     </ItemGroup>
     </ItemGroup>
     
     
     <Hash ItemsToHash="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)">
     <Hash ItemsToHash="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)">
@@ -57,7 +61,11 @@
     </Hash>
     </Hash>
 
 
     <MakeDir Directories="$(IntermediateOutputPath)/Avalonia" />
     <MakeDir Directories="$(IntermediateOutputPath)/Avalonia" />
-    <WriteLinesToFile Overwrite="true" File="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" />
+    <WriteLinesToFile Overwrite="true" File="$(_AvaloniaResourcesInputsCacheFilePath)" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" />
+
+    <ItemGroup>
+      <FileWrites Include="$(_AvaloniaResourcesInputsCacheFilePath)" />
+    </ItemGroup>
   </Target>
   </Target>
   
   
   <Target Name="GenerateAvaloniaResources" 
   <Target Name="GenerateAvaloniaResources" 
@@ -68,7 +76,7 @@
           Condition="('@(AvaloniaResource->Count())' &gt; 0) or ('@(AvaloniaXaml->Count())' &gt; 0)"
           Condition="('@(AvaloniaResource->Count())' &gt; 0) or ('@(AvaloniaXaml->Count())' &gt; 0)"
           >
           >
     <ItemGroup>
     <ItemGroup>
-        <AvaloniaResource Include="@(AvaloniaXaml)"/>
+      <AvaloniaResource Include="@(AvaloniaXaml)" />
     </ItemGroup>
     </ItemGroup>
     <GenerateAvaloniaResourcesTask
     <GenerateAvaloniaResourcesTask
       Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
       Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
@@ -76,6 +84,9 @@
       Root="$(MSBuildProjectDirectory)"
       Root="$(MSBuildProjectDirectory)"
       Resources="@(AvaloniaResource)"
       Resources="@(AvaloniaResource)"
       ReportImportance="$(AvaloniaXamlReportImportance)"/>
       ReportImportance="$(AvaloniaXamlReportImportance)"/>
+    <ItemGroup Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'">
+      <FileWrites Include="$(AvaloniaResourcesTemporaryFilePath)" />
+    </ItemGroup>
     <Exec 
     <Exec 
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
       Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>
       Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>
@@ -103,6 +114,9 @@
       File="$(AvaloniaXamlReferencesTemporaryFilePath)"
       File="$(AvaloniaXamlReferencesTemporaryFilePath)"
       Lines="@(ReferencePathWithRefAssemblies)"
       Lines="@(ReferencePathWithRefAssemblies)"
       Overwrite="true" />
       Overwrite="true" />
+    <ItemGroup Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'">
+      <FileWrites Include="$(AvaloniaXamlReferencesTemporaryFilePath)" />
+    </ItemGroup>
     <CompileAvaloniaXamlTask
     <CompileAvaloniaXamlTask
       Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
       Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
       AssemblyFile="@(IntermediateAssembly)"
       AssemblyFile="@(IntermediateAssembly)"
@@ -117,8 +131,9 @@
       DelaySign="$(DelaySign)"
       DelaySign="$(DelaySign)"
       SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
       SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
       DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
       DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
-      DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
-    />
+      DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)">
+      <Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
+    </CompileAvaloniaXamlTask>
     <Exec
     <Exec
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
       Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
       Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>
       Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>

+ 1 - 1
samples/ControlCatalog.Desktop/Program.cs

@@ -23,7 +23,7 @@ namespace ControlCatalog
         private static void ConfigureAssetAssembly(AppBuilder builder)
         private static void ConfigureAssetAssembly(AppBuilder builder)
         {
         {
             AvaloniaLocator.CurrentMutable
             AvaloniaLocator.CurrentMutable
-                .GetService<IAssetLoader>()
+                .GetRequiredService<IAssetLoader>()
                 .SetDefaultAssembly(typeof(App).Assembly);
                 .SetDefaultAssembly(typeof(App).Assembly);
         }
         }
     }
     }

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

@@ -3,9 +3,11 @@ using System.Collections;
 using Avalonia;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.LogicalTree;
 using Avalonia.Markup.Xaml;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Media.Immutable;
 using Avalonia.Media.Immutable;
+using Avalonia.Platform;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using ControlCatalog.Models;
 using ControlCatalog.Models;
 using ControlCatalog.Pages;
 using ControlCatalog.Pages;
@@ -14,10 +16,14 @@ namespace ControlCatalog
 {
 {
     public class MainView : UserControl
     public class MainView : UserControl
     {
     {
+        private readonly IPlatformSettings _platformSettings;
+
         public MainView()
         public MainView()
         {
         {
             AvaloniaXamlLoader.Load(this);
             AvaloniaXamlLoader.Load(this);
-
+            _platformSettings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
+            PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
+            
             var sideBar = this.Get<TabControl>("Sidebar");
             var sideBar = this.Get<TabControl>("Sidebar");
 
 
             if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
             if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
@@ -37,6 +43,15 @@ namespace ControlCatalog
                 if (themes.SelectedItem is CatalogTheme theme)
                 if (themes.SelectedItem is CatalogTheme theme)
                 {
                 {
                     App.SetThemeVariant(theme);
                     App.SetThemeVariant(theme);
+                    
+                    ((TopLevel?)this.GetVisualRoot())?.PlatformImpl?.SetFrameThemeVariant(theme switch
+                    {
+                        CatalogTheme.FluentLight => PlatformThemeVariant.Light,
+                        CatalogTheme.FluentDark => PlatformThemeVariant.Dark,
+                        CatalogTheme.SimpleLight => PlatformThemeVariant.Light,
+                        CatalogTheme.SimpleDark => PlatformThemeVariant.Dark,
+                        _ => throw new ArgumentOutOfRangeException()
+                    });
                 }
                 }
             };
             };
 
 
@@ -89,6 +104,62 @@ namespace ControlCatalog
             var decorations = this.Get<ComboBox>("Decorations");
             var decorations = this.Get<ComboBox>("Decorations");
             if (VisualRoot is Window window)
             if (VisualRoot is Window window)
                 decorations.SelectedIndex = (int)window.SystemDecorations;
                 decorations.SelectedIndex = (int)window.SystemDecorations;
+            
+            _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
+            PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
+        }
+
+        protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromLogicalTree(e);
+            
+            _platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
+        }
+
+        private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
+        {
+            var themes = this.Get<ComboBox>("Themes");
+            var currentTheme = (CatalogTheme?)themes.SelectedItem ?? CatalogTheme.FluentLight;
+            var newTheme = (currentTheme, e.ThemeVariant) switch
+            {
+                (CatalogTheme.FluentDark, PlatformThemeVariant.Light) => CatalogTheme.FluentLight,
+                (CatalogTheme.FluentLight, PlatformThemeVariant.Dark) => CatalogTheme.FluentDark,
+                (CatalogTheme.SimpleDark, PlatformThemeVariant.Light) => CatalogTheme.SimpleLight,
+                (CatalogTheme.SimpleLight, PlatformThemeVariant.Dark) => CatalogTheme.SimpleDark,
+                _ => currentTheme
+            };
+            themes.SelectedItem = newTheme;
+
+            Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
+            Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
+            Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
+            Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
+            Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
+            Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
+            Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
+
+            static Color ChangeColorLuminosity(Color color, double luminosityFactor)
+            {
+                var red = (double)color.R;
+                var green = (double)color.G;
+                var blue = (double)color.B;
+
+                if (luminosityFactor < 0)
+                {
+                    luminosityFactor = 1 + luminosityFactor;
+                    red *= luminosityFactor;
+                    green *= luminosityFactor;
+                    blue *= luminosityFactor;
+                }
+                else if (luminosityFactor >= 0)
+                {
+                    red = (255 - red) * luminosityFactor + red;
+                    green = (255 - green) * luminosityFactor + green;
+                    blue = (255 - blue) * luminosityFactor + blue;
+                }
+
+                return new Color(color.A, (byte)red, (byte)green, (byte)blue);
+            }
         }
         }
     }
     }
 }
 }

+ 3 - 3
samples/ControlCatalog/Models/StateData.cs

@@ -6,10 +6,10 @@ public class StateData
     public string Abbreviation { get; private set; }
     public string Abbreviation { get; private set; }
     public string Capital { get; private set; }
     public string Capital { get; private set; }
 
 
-    public StateData(string name, string abbreviatoin, string capital)
+    public StateData(string name, string abbreviation, string capital)
     {
     {
         Name = name;
         Name = name;
-        Abbreviation = abbreviatoin;
+        Abbreviation = abbreviation;
         Capital = capital;
         Capital = capital;
     }
     }
 
 
@@ -17,4 +17,4 @@ public class StateData
     {
     {
         return Name;
         return Name;
     }
     }
-}
+}

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

@@ -12,7 +12,7 @@
       </Button>
       </Button>
       <Carousel Name="carousel" Grid.Column="1">
       <Carousel Name="carousel" Grid.Column="1">
         <Carousel.PageTransition>
         <Carousel.PageTransition>
-          <PageSlide Duration="0.25" Orientation="Vertical" />
+          <PageSlide Duration="0.25" Orientation="Horizontal" />
         </Carousel.PageTransition>
         </Carousel.PageTransition>
         <Image Source="/Assets/delicate-arch-896885_640.jpg"/>
         <Image Source="/Assets/delicate-arch-896885_640.jpg"/>
         <Image Source="/Assets/hirsch-899118_640.jpg"/>
         <Image Source="/Assets/hirsch-899118_640.jpg"/>
@@ -35,7 +35,7 @@
 
 
     <StackPanel Orientation="Horizontal" Spacing="4">
     <StackPanel Orientation="Horizontal" Spacing="4">
       <TextBlock VerticalAlignment="Center">Orientation</TextBlock>
       <TextBlock VerticalAlignment="Center">Orientation</TextBlock>
-      <ComboBox Name="orientation" SelectedIndex="1" VerticalAlignment="Center">
+      <ComboBox Name="orientation" SelectedIndex="0" VerticalAlignment="Center">
         <ComboBoxItem>Horizontal</ComboBoxItem>
         <ComboBoxItem>Horizontal</ComboBoxItem>
         <ComboBoxItem>Vertical</ComboBoxItem>
         <ComboBoxItem>Vertical</ComboBoxItem>
       </ComboBox>
       </ComboBox>

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

@@ -46,7 +46,7 @@
                Classes="h2"
                Classes="h2"
                IsVisible="False"
                IsVisible="False"
                Text="Last picker results:" />
                Text="Last picker results:" />
-    <ItemsPresenter x:Name="PickerLastResults" />
+    <ItemsControl x:Name="PickerLastResults" />
 
 
     <TextBox Name="BookmarkContainer" Watermark="Bookmark" />
     <TextBox Name="BookmarkContainer" Watermark="Bookmark" />
     <TextBox Name="OpenedFileContent"
     <TextBox Name="OpenedFileContent"

+ 32 - 32
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@@ -24,7 +24,7 @@ namespace ControlCatalog.Pages
         {
         {
             this.InitializeComponent();
             this.InitializeComponent();
 
 
-            var results = this.Get<ItemsPresenter>("PickerLastResults");
+            var results = this.Get<ItemsControl>("PickerLastResults");
             var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
             var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
             var bookmarkContainer = this.Get<TextBox>("BookmarkContainer");
             var bookmarkContainer = this.Get<TextBox>("BookmarkContainer");
             var openedFileContent = this.Get<TextBox>("OpenedFileContent");
             var openedFileContent = this.Get<TextBox>("OpenedFileContent");
@@ -36,18 +36,18 @@ namespace ControlCatalog.Pages
             {
             {
                 if (this.Get<CheckBox>("UseFilters").IsChecked != true)
                 if (this.Get<CheckBox>("UseFilters").IsChecked != true)
                     return new List<FileDialogFilter>();
                     return new List<FileDialogFilter>();
-                return  new List<FileDialogFilter>
-                {
-                    new FileDialogFilter
-                    {
-                        Name = "Text files (.txt)", Extensions = new List<string> {"txt"}
-                    },
-                    new FileDialogFilter
-                    {
-                        Name = "All files",
-                        Extensions = new List<string> {"*"}
-                    }
-                };
+                return new List<FileDialogFilter>
+                            {
+                                new FileDialogFilter
+                                {
+                                    Name = "Text files (.txt)", Extensions = new List<string> {"txt"}
+                                },
+                                new FileDialogFilter
+                                {
+                                    Name = "All files",
+                                    Extensions = new List<string> {"*"}
+                                }
+                            };
             }
             }
 
 
             List<FilePickerFileType>? GetFileTypes()
             List<FilePickerFileType>? GetFileTypes()
@@ -55,10 +55,10 @@ namespace ControlCatalog.Pages
                 if (this.Get<CheckBox>("UseFilters").IsChecked != true)
                 if (this.Get<CheckBox>("UseFilters").IsChecked != true)
                     return null;
                     return null;
                 return new List<FilePickerFileType>
                 return new List<FilePickerFileType>
-                {
-                    FilePickerFileTypes.All,
-                    FilePickerFileTypes.TextPlain
-                };
+                            {
+                                FilePickerFileTypes.All,
+                                FilePickerFileTypes.TextPlain
+                            };
             }
             }
 
 
             this.Get<Button>("OpenFile").Click += async delegate
             this.Get<Button>("OpenFile").Click += async delegate
@@ -205,15 +205,15 @@ namespace ControlCatalog.Pages
                     await using var stream = await file.OpenWriteAsync();
                     await using var stream = await file.OpenWriteAsync();
                     await using var reader = new System.IO.StreamWriter(stream);
                     await using var reader = new System.IO.StreamWriter(stream);
 #else
 #else
-                    using var stream = await file.OpenWriteAsync();
-                    using var reader = new System.IO.StreamWriter(stream);
+                                using var stream = await file.OpenWriteAsync();
+                                using var reader = new System.IO.StreamWriter(stream);
 #endif
 #endif
                     await reader.WriteLineAsync(openedFileContent.Text);
                     await reader.WriteLineAsync(openedFileContent.Text);
 
 
                     lastSelectedDirectory = await file.GetParentAsync();
                     lastSelectedDirectory = await file.GetParentAsync();
                 }
                 }
 
 
-                await SetPickerResult(file is null ? null : new [] {file});
+                await SetPickerResult(file is null ? null : new[] { file });
             };
             };
             this.Get<Button>("OpenFolderPicker").Click += async delegate
             this.Get<Button>("OpenFolderPicker").Click += async delegate
             {
             {
@@ -243,7 +243,7 @@ namespace ControlCatalog.Pages
                     : null;
                     : null;
 
 
                 await SetPickerResult(folder is null ? null : new[] { folder });
                 await SetPickerResult(folder is null ? null : new[] { folder });
-                
+
                 lastSelectedDirectory = folder;
                 lastSelectedDirectory = folder;
             };
             };
 
 
@@ -260,23 +260,23 @@ namespace ControlCatalog.Pages
 
 
                     var props = await item.GetBasicPropertiesAsync();
                     var props = await item.GetBasicPropertiesAsync();
                     resultText += @$"Size: {props.Size}
                     resultText += @$"Size: {props.Size}
-DateCreated: {props.DateCreated}
-DateModified: {props.DateModified}
-CanBookmark: {item.CanBookmark}
-";
+            DateCreated: {props.DateCreated}
+            DateModified: {props.DateModified}
+            CanBookmark: {item.CanBookmark}
+            ";
                     if (item is IStorageFile file)
                     if (item is IStorageFile file)
                     {
                     {
                         resultText += @$"
                         resultText += @$"
-CanOpenRead: {file.CanOpenRead}
-CanOpenWrite: {file.CanOpenWrite}
-Content:
-";
+            CanOpenRead: {file.CanOpenRead}
+            CanOpenWrite: {file.CanOpenWrite}
+            Content:
+            ";
                         if (file.CanOpenRead)
                         if (file.CanOpenRead)
                         {
                         {
 #if NET6_0_OR_GREATER
 #if NET6_0_OR_GREATER
                             await using var stream = await file.OpenReadAsync();
                             await using var stream = await file.OpenReadAsync();
 #else
 #else
-                            using var stream = await file.OpenReadAsync();
+                                        using var stream = await file.OpenReadAsync();
 #endif
 #endif
                             using var reader = new System.IO.StreamReader(stream);
                             using var reader = new System.IO.StreamReader(stream);
 
 
@@ -396,8 +396,8 @@ CanPickFolder: {storageProvider.CanPickFolder}";
             return item.TryGetUri(out var uri) ? uri.ToString() : item.Name;
             return item.TryGetUri(out var uri) ? uri.ToString() : item.Name;
         }
         }
 
 
-        Window GetWindow() => this.VisualRoot as Window ?? throw new NullReferenceException("Invalid Owner");
-        TopLevel GetTopLevel() => this.VisualRoot as TopLevel ?? throw new NullReferenceException("Invalid Owner");
+        Window GetWindow() => TopLevel.GetTopLevel(this) as Window ?? throw new NullReferenceException("Invalid Owner");
+        TopLevel GetTopLevel() => TopLevel.GetTopLevel(this) ?? throw new NullReferenceException("Invalid Owner");
 
 
         private void InitializeComponent()
         private void InitializeComponent()
         {
         {

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

@@ -1,6 +1,5 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Reactive;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using System.Windows.Input;
 using System.Windows.Input;
 using Avalonia.Controls;
 using Avalonia.Controls;

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

@@ -27,7 +27,7 @@ namespace ControlCatalog.Pages
         {
         {
             base.OnAttachedToVisualTree(e);
             base.OnAttachedToVisualTree(e);
 
 
-            _viewModel.NotificationManager = new Avalonia.Controls.Notifications.WindowNotificationManager(VisualRoot as TopLevel);
+            _viewModel.NotificationManager = new Avalonia.Controls.Notifications.WindowNotificationManager(TopLevel.GetTopLevel(this));
         }
         }
     }
     }
 }
 }

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

@@ -24,7 +24,7 @@ public class PointerCanvas : Control
     {
     {
         struct CanvasPoint
         struct CanvasPoint
         {
         {
-            public IBrush Brush;
+            public IBrush? Brush;
             public Point Point;
             public Point Point;
             public double Radius;
             public double Radius;
             public double? Pressure;
             public double? Pressure;

+ 0 - 1
samples/ControlCatalog/Pages/PointerContactsTab.cs

@@ -2,7 +2,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using System.Reactive.Linq;
 
 
 using Avalonia;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;

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

@@ -36,44 +36,44 @@ namespace ControlCatalog.Pages
 
 
             var drawBrush = Brushes.Black;
             var drawBrush = Brushes.Black;
             Pen p = new Pen(drawBrush);
             Pen p = new Pen(drawBrush);
-            if (screens != null)
-                foreach (Screen screen in screens)
+
+            foreach (Screen screen in screens)
+            {
+                if (screen.Bounds.X / 10f < _leftMost)
                 {
                 {
-                    if (screen.Bounds.X / 10f < _leftMost)
-                    {
-                        _leftMost = screen.Bounds.X / 10f;
-                        Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
-                        return;
-                    }
+                    _leftMost = screen.Bounds.X / 10f;
+                    Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
+                    return;
+                }
 
 
-                    Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f,
-                                      screen.Bounds.Height / 10f);
-                    Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
-                                       screen.WorkingArea.Height / 10f);
-                    
-                    context.DrawRectangle(p, boundsRect);
-                    context.DrawRectangle(p, workingAreaRect);
+                Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f,
+                                  screen.Bounds.Height / 10f);
+                Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
+                                   screen.WorkingArea.Height / 10f);
 
 
+                context.DrawRectangle(p, boundsRect);
+                context.DrawRectangle(p, workingAreaRect);
 
 
-                    var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
-                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height));
 
 
-                    formattedText =
-                        CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
-                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
+                var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
+                context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height));
 
 
-                    formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%");
-                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
+                formattedText =
+                    CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
+                context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
 
 
-                    formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");
+                formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%");
+                context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
 
 
-                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
+                formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");
 
 
-                    formattedText =
-                        CreateFormattedText(
-                            $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}");
-                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80));
-                }
+                context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
+
+                formattedText =
+                    CreateFormattedText(
+                        $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}");
+                context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80));
+            }
 
 
             context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
             context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
         }
         }

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

@@ -53,7 +53,7 @@
                 <TabControl
                 <TabControl
                     Items="{Binding Tabs}"
                     Items="{Binding Tabs}"
                     Margin="0 16"
                     Margin="0 16"
-                    HeaderDisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
+                    DisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
                     TabStripPlacement="{Binding TabPlacement}">
                     TabStripPlacement="{Binding TabPlacement}">
                     <TabControl.ContentTemplate>
                     <TabControl.ContentTemplate>
                         <DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
                         <DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">

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

@@ -51,7 +51,7 @@ namespace ControlCatalog.Pages
 
 
         private static IBitmap LoadBitmap(string uri)
         private static IBitmap LoadBitmap(string uri)
         {
         {
-            var assets = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
+            var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
             return new Bitmap(assets.Open(new Uri(uri)));
             return new Bitmap(assets.Open(new Uri(uri)));
         }
         }
     }
     }

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

@@ -38,7 +38,7 @@
                  UseFloatingWatermark="True"
                  UseFloatingWatermark="True"
                  PasswordChar="*"
                  PasswordChar="*"
                  Text="Password" />
                  Text="Password" />
-        <TextBox Width="200" Text="Left aligned text" TextAlignment="Left" />
+        <TextBox Width="200" Text="Left aligned text" TextAlignment="Left" AcceptsTab="True" />
         <TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
         <TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
         <TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
         <TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
         <TextBox Width="200" Text="Custom selection brush"
         <TextBox Width="200" Text="Custom selection brush"

+ 0 - 1
samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs

@@ -1,7 +1,6 @@
 using System;
 using System;
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
-using System.Reactive;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Selection;
 using Avalonia.Controls.Selection;
 using MiniMvvm;
 using MiniMvvm;

+ 2 - 6
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Reactive;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
@@ -56,12 +55,9 @@ namespace ControlCatalog.ViewModels
 
 
             var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
             var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true });
 
 
-            if (result != null)
+            foreach (var file in result)
             {
             {
-                foreach (var file in result)
-                {
-                    System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}");
-                }
+                System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}");
             }
             }
         }
         }
 
 

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

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

+ 0 - 1
samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs

@@ -1,7 +1,6 @@
 using System;
 using System;
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
-using System.Reactive;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Selection;
 using Avalonia.Controls.Selection;
 using ControlCatalog.Pages;
 using ControlCatalog.Pages;

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

@@ -1,9 +1,9 @@
-using System.Reactive;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Notifications;
 using Avalonia.Controls.Notifications;
 using Avalonia.Dialogs;
 using Avalonia.Dialogs;
 using Avalonia.Platform;
 using Avalonia.Platform;
+using Avalonia.Reactive;
 using System;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using MiniMvvm;
 using MiniMvvm;

+ 0 - 2
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@@ -1,6 +1,4 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Reactive;
-using System.Reactive.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;

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

@@ -1,5 +1,4 @@
-using System.Reactive;
-using Avalonia.Controls.Notifications;
+using Avalonia.Controls.Notifications;
 using MiniMvvm;
 using MiniMvvm;
 
 
 namespace ControlCatalog.ViewModels
 namespace ControlCatalog.ViewModels

+ 0 - 1
samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs

@@ -1,6 +1,5 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
-using System.Reactive;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Avalonia.Controls.Notifications;
 using Avalonia.Controls.Notifications;
 using ControlCatalog.Pages;
 using ControlCatalog.Pages;

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

@@ -19,7 +19,7 @@ namespace ControlCatalog.ViewModels
     {
     {
         public TransitioningContentControlPageViewModel()
         public TransitioningContentControlPageViewModel()
         {
         {
-            var assetLoader = AvaloniaLocator.Current?.GetService<IAssetLoader>()!;
+            var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
 
 
             var images = new string[] 
             var images = new string[] 
             { 
             { 

+ 0 - 1
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@@ -1,7 +1,6 @@
 using System;
 using System;
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
-using System.Reactive;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using MiniMvvm;
 using MiniMvvm;
 
 

+ 2 - 3
samples/Directory.Build.props

@@ -2,9 +2,8 @@
   <PropertyGroup>
   <PropertyGroup>
       <IsPackable>false</IsPackable>
       <IsPackable>false</IsPackable>
       <AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
       <AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
+      <EnableNETAnalyzers>false</EnableNETAnalyzers>
+      <LangVersion>11</LangVersion>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="..\build\SharedVersion.props" />
   <Import Project="..\build\SharedVersion.props" />
-  <PropertyGroup>
-    <EnableNETAnalyzers>false</EnableNETAnalyzers>
-  </PropertyGroup>  
 </Project>
 </Project>

+ 3 - 1
samples/MiniMvvm/MiniMvvm.csproj

@@ -2,5 +2,7 @@
   <PropertyGroup>
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
     <TargetFramework>netstandard2.0</TargetFramework>
   </PropertyGroup>
   </PropertyGroup>
-  <Import Project="..\..\build\Rx.props" />
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+  </ItemGroup>
 </Project>
 </Project>

+ 8 - 6
samples/MiniMvvm/PropertyChangedExtensions.cs

@@ -1,8 +1,8 @@
 using System;
 using System;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Linq.Expressions;
 using System.Linq.Expressions;
-using System.Reactive.Linq;
 using System.Reflection;
 using System.Reflection;
+using Avalonia.Reactive;
 
 
 namespace MiniMvvm
 namespace MiniMvvm
 {
 {
@@ -92,11 +92,13 @@ namespace MiniMvvm
             Expression<Func<TModel, T3>> v3,
             Expression<Func<TModel, T3>> v3,
             Func<T1, T2, T3, TRes> cb
             Func<T1, T2, T3, TRes> cb
         ) where TModel : INotifyPropertyChanged =>
         ) where TModel : INotifyPropertyChanged =>
-            Observable.CombineLatest(
-                model.WhenAnyValue(v1),
-                model.WhenAnyValue(v2),
-                model.WhenAnyValue(v3),
-                cb);
+            model.WhenAnyValue(v1)
+                .CombineLatest(
+                    model.WhenAnyValue(v2),
+                    (l, r) => (l, r))
+                .CombineLatest(
+                    model.WhenAnyValue(v3),
+                    (t, r) => cb(t.l, t.r, r));
 
 
         public static IObservable<ValueTuple<T1, T2, T3>> WhenAnyValue<TModel, T1, T2, T3>(this TModel model,
         public static IObservable<ValueTuple<T1, T2, T3>> WhenAnyValue<TModel, T1, T2, T3>(this TModel model,
             Expression<Func<TModel, T1>> v1,
             Expression<Func<TModel, T1>> v1,

+ 0 - 1
samples/MiniMvvm/ViewModelBase.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
-using System.Reactive.Joins;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 
 
 namespace MiniMvvm
 namespace MiniMvvm

+ 1 - 0
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@@ -11,4 +11,5 @@
     <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <Import Project="..\..\build\Rx.props" />
 </Project>
 </Project>

+ 2 - 1
samples/Previewer/Previewer.csproj

@@ -12,7 +12,8 @@
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
   </ItemGroup>
   </ItemGroup>
-  
+
+  <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\ReferenceCoreLibraries.props" />
   <Import Project="..\..\build\ReferenceCoreLibraries.props" />
 </Project>
 </Project>

+ 0 - 1
samples/ReactiveUIDemo/ReactiveUIDemo.csproj

@@ -23,6 +23,5 @@
   <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\ReferenceCoreLibraries.props" />
   <Import Project="..\..\build\ReferenceCoreLibraries.props" />
   <Import Project="..\..\build\BuildTargets.targets" />
   <Import Project="..\..\build\BuildTargets.targets" />
-  <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
 </Project>
 </Project>

+ 1 - 3
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@@ -168,9 +168,7 @@
                               HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
                               HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
                               VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
                               VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
                   <ItemsPresenter Name="PART_ItemsPresenter"
                   <ItemsPresenter Name="PART_ItemsPresenter"
-                                  HorizontalAlignment="Stretch"
-                                  ItemTemplate="{TemplateBinding ItemTemplate}"
-                                  Items="{TemplateBinding Items}">
+                                  HorizontalAlignment="Stretch">
                     <ItemsPresenter.ItemsPanel>
                     <ItemsPresenter.ItemsPanel>
                       <ItemsPanelTemplate>
                       <ItemsPanelTemplate>
                         <StackPanel x:Name="HamburgerItemsPanel"
                         <StackPanel x:Name="HamburgerItemsPanel"

+ 1 - 4
samples/VirtualizationDemo/MainWindow.xaml

@@ -9,10 +9,8 @@
     <DockPanel LastChildFill="True" Margin="16">
     <DockPanel LastChildFill="True" Margin="16">
         <StackPanel DockPanel.Dock="Right" 
         <StackPanel DockPanel.Dock="Right" 
                     Margin="16 0 0 0" 
                     Margin="16 0 0 0" 
-                    MinWidth="150"
+                    Width="150"
                     Spacing="4">
                     Spacing="4">
-            <ComboBox Items="{Binding VirtualizationModes}"
-                      SelectedItem="{Binding VirtualizationMode}"/>
             <ComboBox Items="{Binding Orientations}"
             <ComboBox Items="{Binding Orientations}"
                       SelectedItem="{Binding Orientation}"/>
                       SelectedItem="{Binding Orientation}"/>
             <TextBox Watermark="Item Count"
             <TextBox Watermark="Item Count"
@@ -49,7 +47,6 @@
                  Items="{Binding Items}" 
                  Items="{Binding Items}" 
                  Selection="{Binding Selection}"
                  Selection="{Binding Selection}"
                  SelectionMode="Multiple"
                  SelectionMode="Multiple"
-                 VirtualizationMode="{Binding VirtualizationMode}"
                  ScrollViewer.HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility, Mode=TwoWay}"
                  ScrollViewer.HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility, Mode=TwoWay}"
                  ScrollViewer.VerticalScrollBarVisibility="{Binding VerticalScrollBarVisibility, Mode=TwoWay}">
                  ScrollViewer.VerticalScrollBarVisibility="{Binding VerticalScrollBarVisibility, Mode=TwoWay}">
             <ListBox.ItemsPanel>
             <ListBox.ItemsPanel>

+ 0 - 10
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@@ -21,7 +21,6 @@ namespace VirtualizationDemo.ViewModels
         private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
         private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
         private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
         private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
         private Orientation _orientation = Orientation.Vertical;
         private Orientation _orientation = Orientation.Vertical;
-        private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
 
 
         public MainWindowViewModel()
         public MainWindowViewModel()
         {
         {
@@ -81,15 +80,6 @@ namespace VirtualizationDemo.ViewModels
         public IEnumerable<ScrollBarVisibility> ScrollBarVisibilities =>
         public IEnumerable<ScrollBarVisibility> ScrollBarVisibilities =>
             Enum.GetValues(typeof(ScrollBarVisibility)).Cast<ScrollBarVisibility>();
             Enum.GetValues(typeof(ScrollBarVisibility)).Cast<ScrollBarVisibility>();
 
 
-        public ItemVirtualizationMode VirtualizationMode
-        {
-            get { return _virtualizationMode; }
-            set { this.RaiseAndSetIfChanged(ref _virtualizationMode, value); }
-        }
-
-        public IEnumerable<ItemVirtualizationMode> VirtualizationModes => 
-            Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
-
         public MiniCommand AddItemCommand { get; private set; }
         public MiniCommand AddItemCommand { get; private set; }
         public MiniCommand RecreateCommand { get; private set; }
         public MiniCommand RecreateCommand { get; private set; }
         public MiniCommand RemoveItemCommand { get; private set; }
         public MiniCommand RemoveItemCommand { get; private set; }

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

@@ -15,6 +15,4 @@
       <Name>ControlCatalog</Name>
       <Name>ControlCatalog</Name>
     </ProjectReference>
     </ProjectReference>
   </ItemGroup>
   </ItemGroup>
-
-  <Import Project="..\..\..\build\Rx.props" />
 </Project>
 </Project>

+ 1 - 1
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -43,7 +43,7 @@ namespace Avalonia.Android
                 .Bind<ICursorFactory>().ToTransient<CursorFactory>()
                 .Bind<ICursorFactory>().ToTransient<CursorFactory>()
                 .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
                 .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
                 .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
                 .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
-                .Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
+                .Bind<IPlatformSettings>().ToSingleton<AndroidPlatformSettings>()
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
                 .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
                 .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
                 .Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
                 .Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())

+ 1 - 1
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@@ -1,10 +1,10 @@
 using System;
 using System;
-using System.Reactive.Disposables;
 using System.Threading;
 using System.Threading;
 
 
 using Android.OS;
 using Android.OS;
 
 
 using Avalonia.Platform;
 using Avalonia.Platform;
+using Avalonia.Reactive;
 using Avalonia.Threading;
 using Avalonia.Threading;
 
 
 using App = Android.App.Application;
 using App = Android.App.Application;

+ 12 - 7
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@@ -1,18 +1,14 @@
 using System;
 using System;
 using Android.App;
 using Android.App;
 using Android.Content;
 using Android.Content;
-using Android.Content.Res;
 using Android.OS;
 using Android.OS;
 using Android.Runtime;
 using Android.Runtime;
 using Android.Views;
 using Android.Views;
 using AndroidX.AppCompat.App;
 using AndroidX.AppCompat.App;
-using AndroidX.Lifecycle;
-
-using AndroidRect = Android.Graphics.Rect;
 
 
 namespace Avalonia.Android
 namespace Avalonia.Android
 {
 {
-    public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
+    public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
     {
     {
         internal static object ViewContent;
         internal static object ViewContent;
 
 
@@ -56,9 +52,18 @@ namespace Avalonia.Android
             }
             }
         }
         }
 
 
-        public override void OnConfigurationChanged(Configuration newConfig)
+        public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
+
+        public override void OnBackPressed()
         {
         {
-            base.OnConfigurationChanged(newConfig);
+            var eventArgs = new AndroidBackRequestedEventArgs();
+
+            BackRequested?.Invoke(this, eventArgs);
+
+            if (!eventArgs.Handled)
+            {
+                base.OnBackPressed();
+            }
         }
         }
 
 
         protected override void OnDestroy()
         protected override void OnDestroy()

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

@@ -1,11 +1,14 @@
 using System;
 using System;
 using Android.Content;
 using Android.Content;
+using Android.Content.Res;
 using Android.Runtime;
 using Android.Runtime;
 using Android.Views;
 using Android.Views;
 using Android.Widget;
 using Android.Widget;
+using Avalonia.Android.Platform;
 using Avalonia.Android.Platform.SkiaPlatform;
 using Avalonia.Android.Platform.SkiaPlatform;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Embedding;
 using Avalonia.Controls.Embedding;
+using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Rendering;
 
 
 namespace Avalonia.Android
 namespace Avalonia.Android
@@ -26,6 +29,7 @@ namespace Avalonia.Android
             _root.Prepare();
             _root.Prepare();
 
 
             this.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
             this.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
+            OnConfigurationChanged();
         }
         }
 
 
         internal TopLevelImpl TopLevelImpl => _view;
         internal TopLevelImpl TopLevelImpl => _view;
@@ -70,6 +74,18 @@ namespace Avalonia.Android
                 _timerSubscription?.Dispose();
                 _timerSubscription?.Dispose();
             }
             }
         }
         }
+        
+        protected override void OnConfigurationChanged(Configuration newConfig)
+        {
+            base.OnConfigurationChanged(newConfig);
+            OnConfigurationChanged();
+        }
+
+        private void OnConfigurationChanged()
+        {
+            var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
+            settings?.OnViewConfigurationChanged(Context);
+        }
 
 
         class ViewImpl : TopLevelImpl
         class ViewImpl : TopLevelImpl
         {
         {

+ 1 - 1
src/Android/Avalonia.Android/ChoreographerTimer.cs

@@ -1,11 +1,11 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Reactive.Disposables;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 using Android.OS;
 using Android.OS;
 using Android.Views;
 using Android.Views;
 
 
+using Avalonia.Reactive;
 using Avalonia.Rendering;
 using Avalonia.Rendering;
 
 
 using Java.Lang;
 using Java.Lang;

+ 14 - 0
src/Android/Avalonia.Android/IAndroidNavigationService.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Avalonia.Android
+{
+    public interface IActivityNavigationService
+    {
+        event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
+    }
+
+    public class AndroidBackRequestedEventArgs : EventArgs
+    {
+        public bool Handled { get; set; }
+    }
+}

+ 94 - 0
src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs

@@ -0,0 +1,94 @@
+using System;
+using Android;
+using Android.Content;
+using Android.Content.Res;
+using Android.Graphics;
+using Android.Provider;
+using Android.Views.Accessibility;
+using AndroidX.Core.Content.Resources;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Color = Avalonia.Media.Color;
+
+namespace Avalonia.Android.Platform;
+
+// TODO: ideally should be created per view/activity.
+internal class AndroidPlatformSettings : DefaultPlatformSettings
+{
+    private PlatformColorValues _latestValues;
+
+    public AndroidPlatformSettings()
+    {
+        _latestValues = base.GetColorValues();
+    }
+
+    public override PlatformColorValues GetColorValues()
+    {
+        return _latestValues;
+    }
+
+    internal void OnViewConfigurationChanged(Context context)
+    {
+        if (context.Resources?.Configuration is null)
+        {
+            return;
+        }
+
+        var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch
+        {
+            UiMode.NightYes => PlatformThemeVariant.Dark,
+            UiMode.NightNo => PlatformThemeVariant.Light,
+            _ => throw new ArgumentOutOfRangeException()
+        };
+
+        if (OperatingSystem.IsAndroidVersionAtLeast(31))
+        {
+            // See https://developer.android.com/reference/android/R.color
+            var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500
+            var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500
+            var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500
+
+            _latestValues = new PlatformColorValues
+            {
+                ThemeVariant = systemTheme,
+                ContrastPreference = IsHighContrast(context),
+                AccentColor1 = new Color(accent1.A, accent1.R, accent1.G, accent1.B),
+                AccentColor2 = new Color(accent2.A, accent2.R, accent2.G, accent2.B),
+                AccentColor3 = new Color(accent3.A, accent3.R, accent3.G, accent3.B),
+            };
+        }
+        else if (OperatingSystem.IsAndroidVersionAtLeast(23))
+        {
+            // See https://developer.android.com/reference/android/R.attr
+            var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
+            var accent = array.GetColor(0, 0);
+
+            _latestValues = new PlatformColorValues
+            {
+                ThemeVariant = systemTheme,
+                ContrastPreference = IsHighContrast(context),
+                AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B)
+            };
+            array.Recycle();
+        }
+        else
+        {
+            _latestValues = _latestValues with { ThemeVariant = systemTheme };
+        }
+
+        OnColorValuesChanged(_latestValues);
+    }
+
+    private static ColorContrastPreference IsHighContrast(Context context)
+    {
+        try
+        {
+            return Settings.Secure.GetInt(context.ContentResolver, "high_text_contrast_enabled", 0) == 1
+                ? ColorContrastPreference.High : ColorContrastPreference.NoPreference;
+        }
+        catch
+        {
+            return ColorContrastPreference.NoPreference;
+        }
+    }
+}

+ 28 - 0
src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs

@@ -0,0 +1,28 @@
+using System;
+using Avalonia.Interactivity;
+using Avalonia.Platform;
+
+namespace Avalonia.Android.Platform
+{
+    internal class AndroidSystemNavigationManager : ISystemNavigationManager
+    {
+        public event EventHandler<RoutedEventArgs> BackRequested;
+
+        public AndroidSystemNavigationManager(IActivityNavigationService? navigationService)
+        {
+            if(navigationService != null)
+            {
+                navigationService.BackRequested += OnBackRequested;
+            }
+        }
+
+        private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e)
+        {
+            var routedEventArgs = new RoutedEventArgs();
+
+            BackRequested?.Invoke(this, routedEventArgs);
+
+            e.Handled = routedEventArgs.Handled;
+        }
+    }
+}

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

@@ -26,12 +26,14 @@ using Avalonia.Rendering.Composition;
 using Java.Lang;
 using Java.Lang;
 using Math = System.Math;
 using Math = System.Math;
 using AndroidRect = Android.Graphics.Rect;
 using AndroidRect = Android.Graphics.Rect;
+using Window = Android.Views.Window;
 using Android.Graphics.Drawables;
 using Android.Graphics.Drawables;
 
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
 {
     class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
     class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
-        ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
+        ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
+        ITopLevelWithSystemNavigationManager
     {
     {
         private readonly IGlPlatformSurface _gl;
         private readonly IGlPlatformSurface _gl;
         private readonly IFramebufferPlatformSurface _framebuffer;
         private readonly IFramebufferPlatformSurface _framebuffer;
@@ -57,6 +59,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
 
             NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
             NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
             StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
             StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
+
+            SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService);
         }
         }
 
 
         public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
         public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@@ -286,6 +290,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
 
         public WindowTransparencyLevel TransparencyLevel { get; private set; }
         public WindowTransparencyLevel TransparencyLevel { get; private set; }
 
 
+        public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
+        {
+            // TODO adjust status bar depending on full screen mode.
+        }
+
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
 
 
         IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
         IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
@@ -300,6 +309,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         
         
         public IStorageProvider StorageProvider { get; }
         public IStorageProvider StorageProvider { get; }
 
 
+        public ISystemNavigationManager SystemNavigationManager { get; }
+
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
         {
         {
             if (TransparencyLevel != transparencyLevel)
             if (TransparencyLevel != transparencyLevel)

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

@@ -2,8 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Linq;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
+using Avalonia.Reactive;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 

+ 1 - 3
src/Avalonia.Base/Animation/AnimationInstance`1.cs

@@ -1,10 +1,8 @@
 using System;
 using System;
 using System.Linq;
 using System.Linq;
-using System.Reactive.Linq;
+using Avalonia.Reactive;
 using Avalonia.Animation.Animators;
 using Avalonia.Animation.Animators;
-using Avalonia.Animation.Utils;
 using Avalonia.Data;
 using Avalonia.Data;
-using Avalonia.Reactive;
 
 
 namespace Avalonia.Animation
 namespace Avalonia.Animation
 {
 {

+ 1 - 1
src/Avalonia.Base/Animation/AnimatorKeyFrame.cs

@@ -63,7 +63,7 @@ namespace Avalonia.Animation
             }
             }
             else
             else
             {
             {
-                return this.Bind(ValueProperty, ObservableEx.SingleValue(value).ToBinding(), targetControl);
+                return this.Bind(ValueProperty, Observable.SingleValue(value).ToBinding(), targetControl);
             }
             }
         }
         }
 
 

+ 0 - 2
src/Avalonia.Base/Animation/Animators/Animator`1.cs

@@ -1,8 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
 using Avalonia.Animation.Utils;
 using Avalonia.Animation.Utils;
 using Avalonia.Collections;
 using Avalonia.Collections;
 using Avalonia.Data;
 using Avalonia.Data;

+ 1 - 1
src/Avalonia.Base/Animation/Animators/ColorAnimator.cs

@@ -2,7 +2,7 @@
 // and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp).
 // and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp).
 
 
 using System;
 using System;
-using System.Reactive.Disposables;
+using Avalonia.Reactive;
 using Avalonia.Logging;
 using Avalonia.Logging;
 using Avalonia.Media;
 using Avalonia.Media;
 
 

+ 1 - 1
src/Avalonia.Base/Animation/Animators/TransformAnimator.cs

@@ -1,5 +1,5 @@
 using System;
 using System;
-using System.Reactive.Disposables;
+using Avalonia.Reactive;
 using Avalonia.Logging;
 using Avalonia.Logging;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Media.Transformation;
 using Avalonia.Media.Transformation;

+ 1 - 0
src/Avalonia.Base/Animation/Clock.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Animation
 namespace Avalonia.Animation
 {
 {

+ 2 - 2
src/Avalonia.Base/Animation/CrossFade.cs

@@ -1,6 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Reactive.Disposables;
+using Avalonia.Reactive;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Avalonia.Animation.Easings;
 using Avalonia.Animation.Easings;
@@ -108,7 +108,7 @@ namespace Avalonia.Animation
             }
             }
 
 
             var tasks = new List<Task>();
             var tasks = new List<Task>();
-            using (var disposables = new CompositeDisposable())
+            using (var disposables = new CompositeDisposable(1))
             {
             {
                 if (to != null)
                 if (to != null)
                 {
                 {

+ 0 - 2
src/Avalonia.Base/Animation/PageSlide.cs

@@ -79,7 +79,6 @@ namespace Avalonia.Animation
                 var animation = new Animation
                 var animation = new Animation
                 {
                 {
                     Easing = SlideOutEasing,
                     Easing = SlideOutEasing,
-                    FillMode = FillMode.Forward,
                     Children =
                     Children =
                     {
                     {
                         new KeyFrame
                         new KeyFrame
@@ -110,7 +109,6 @@ namespace Avalonia.Animation
                 to.IsVisible = true;
                 to.IsVisible = true;
                 var animation = new Animation
                 var animation = new Animation
                 {
                 {
-                    FillMode = FillMode.Forward,
                     Easing = SlideInEasing,
                     Easing = SlideInEasing,
                     Children =
                     Children =
                     {
                     {

+ 0 - 2
src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs

@@ -71,7 +71,6 @@ public class Rotate3DTransition: PageSlide
             {
             {
                 Easing = SlideOutEasing,
                 Easing = SlideOutEasing,
                 Duration = Duration,
                 Duration = Duration,
-                FillMode = FillMode.Forward,
                 Children =
                 Children =
                 {
                 {
                     CreateKeyFrame(0d, 0d, 2),
                     CreateKeyFrame(0d, 0d, 2),
@@ -90,7 +89,6 @@ public class Rotate3DTransition: PageSlide
             {
             {
                 Easing = SlideInEasing,
                 Easing = SlideInEasing,
                 Duration = Duration,
                 Duration = Duration,
-                FillMode = FillMode.Forward,
                 Children =
                 Children =
                 {
                 {
                     CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1),
                     CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1),

+ 11 - 1
src/Avalonia.Base/Avalonia.Base.csproj

@@ -14,7 +14,6 @@
   </ItemGroup>
   </ItemGroup>
   <Import Project="..\..\build\Base.props" />
   <Import Project="..\..\build\Base.props" />
   <Import Project="..\..\build\Binding.props" />
   <Import Project="..\..\build\Binding.props" />
-  <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\System.Memory.props" />
   <Import Project="..\..\build\System.Memory.props" />
   <Import Project="..\..\build\ApiDiff.props" />
   <Import Project="..\..\build\ApiDiff.props" />
   <Import Project="..\..\build\NullableEnable.props" />
   <Import Project="..\..\build\NullableEnable.props" />
@@ -37,6 +36,13 @@
     <InternalsVisibleTo Include="Avalonia.Skia, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Skia, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.ColorPicker, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.ColorPicker, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.DataGrid, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.DataGrid, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Headless, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Native, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.FreeDesktop, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Browser, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.OpenGL, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Skia, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Direct2D1.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Direct2D1.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
@@ -48,8 +54,12 @@
     <InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Android, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.iOS, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="MiniMvvm, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="ControlCatalog, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
     <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
   </ItemGroup>
   </ItemGroup>
   
   

+ 1 - 1
src/Avalonia.Base/AvaloniaObject.cs

@@ -621,7 +621,7 @@ namespace Avalonia
         /// <param name="oldValue">The old property value.</param>
         /// <param name="oldValue">The old property value.</param>
         /// <param name="newValue">The new property value.</param>
         /// <param name="newValue">The new property value.</param>
         /// <param name="priority">The priority of the binding that produced the value.</param>
         /// <param name="priority">The priority of the binding that produced the value.</param>
-        private protected void RaisePropertyChanged<T>(
+        protected void RaisePropertyChanged<T>(
             DirectPropertyBase<T> property,
             DirectPropertyBase<T> property,
             Optional<T> oldValue,
             Optional<T> oldValue,
             BindingValue<T> newValue,
             BindingValue<T> newValue,

+ 55 - 121
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -1,10 +1,6 @@
 using System;
 using System;
-using System.Reactive;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using Avalonia.Data;
 using Avalonia.Reactive;
 using Avalonia.Reactive;
+using Avalonia.Data;
 
 
 namespace Avalonia
 namespace Avalonia
 {
 {
@@ -127,108 +123,6 @@ namespace Avalonia
                 property ?? throw new ArgumentNullException(nameof(property)));
                 property ?? throw new ArgumentNullException(nameof(property)));
         }
         }
 
 
-        /// <summary>
-        /// Gets a subject for an <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <param name="o">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="priority">
-        /// The priority with which binding values are written to the object.
-        /// </param>
-        /// <returns>
-        /// An <see cref="ISubject{Object}"/> which can be used for two-way binding to/from the 
-        /// property.
-        /// </returns>
-        public static ISubject<object?> GetSubject(
-            this AvaloniaObject o,
-            AvaloniaProperty property,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            return Subject.Create<object?>(
-                Observer.Create<object?>(x => o.SetValue(property, x, priority)),
-                o.GetObservable(property));
-        }
-
-        /// <summary>
-        /// Gets a subject for an <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <typeparam name="T">The property type.</typeparam>
-        /// <param name="o">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="priority">
-        /// The priority with which binding values are written to the object.
-        /// </param>
-        /// <returns>
-        /// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the 
-        /// property.
-        /// </returns>
-        public static ISubject<T> GetSubject<T>(
-            this AvaloniaObject o,
-            AvaloniaProperty<T> property,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            return Subject.Create<T>(
-                Observer.Create<T>(x => o.SetValue(property, x, priority)),
-                o.GetObservable(property));
-        }
-
-        /// <summary>
-        /// Gets a subject for a <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <param name="o">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="priority">
-        /// The priority with which binding values are written to the object.
-        /// </param>
-        /// <returns>
-        /// An <see cref="ISubject{Object}"/> which can be used for two-way binding to/from the 
-        /// property.
-        /// </returns>
-        public static ISubject<BindingValue<object?>> GetBindingSubject(
-            this AvaloniaObject o,
-            AvaloniaProperty property,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            return Subject.Create<BindingValue<object?>>(
-                Observer.Create<BindingValue<object?>>(x =>
-                {
-                    if (x.HasValue)
-                    {
-                        o.SetValue(property, x.Value, priority);
-                    }
-                }),
-                o.GetBindingObservable(property));
-        }
-
-        /// <summary>
-        /// Gets a subject for a <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <typeparam name="T">The property type.</typeparam>
-        /// <param name="o">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="priority">
-        /// The priority with which binding values are written to the object.
-        /// </param>
-        /// <returns>
-        /// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the 
-        /// property.
-        /// </returns>
-        public static ISubject<BindingValue<T>> GetBindingSubject<T>(
-            this AvaloniaObject o,
-            AvaloniaProperty<T> property,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            return Subject.Create<BindingValue<T>>(
-                Observer.Create<BindingValue<T>>(x =>
-                {
-                    if (x.HasValue)
-                    {
-                        o.SetValue(property, x.Value, priority);
-                    }
-                }),
-                o.GetBindingObservable(property));
-        }
-
         /// <summary>
         /// <summary>
         /// Binds an <see cref="AvaloniaProperty"/> to an observable.
         /// Binds an <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
         /// </summary>
@@ -407,13 +301,7 @@ namespace Avalonia
             Action<TTarget, AvaloniaPropertyChangedEventArgs> action)
             Action<TTarget, AvaloniaPropertyChangedEventArgs> action)
             where TTarget : AvaloniaObject
             where TTarget : AvaloniaObject
         {
         {
-            return observable.Subscribe(e =>
-            {
-                if (e.Sender is TTarget target)
-                {
-                    action(target, e);
-                }
-            });
+            return observable.Subscribe(new ClassHandlerObserver<TTarget>(action));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -431,13 +319,7 @@ namespace Avalonia
             this IObservable<AvaloniaPropertyChangedEventArgs<TValue>> observable,
             this IObservable<AvaloniaPropertyChangedEventArgs<TValue>> observable,
             Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action) where TTarget : AvaloniaObject
             Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action) where TTarget : AvaloniaObject
         {
         {
-            return observable.Subscribe(e =>
-            {
-                if (e.Sender is TTarget target)
-                {
-                    action(target, e);
-                }
-            });
+            return observable.Subscribe(new ClassHandlerObserver<TTarget, TValue>(action));
         }
         }
 
 
         private class BindingAdaptor : IBinding
         private class BindingAdaptor : IBinding
@@ -458,5 +340,57 @@ namespace Avalonia
                 return InstancedBinding.OneWay(_source);
                 return InstancedBinding.OneWay(_source);
             }
             }
         }
         }
+        
+        private class ClassHandlerObserver<TTarget, TValue> : IObserver<AvaloniaPropertyChangedEventArgs<TValue>>
+        {
+            private readonly Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> _action;
+
+            public ClassHandlerObserver(Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action)
+            {
+                _action = action;
+            }
+
+            public void OnCompleted()
+            {
+            }
+
+            public void OnError(Exception error)
+            {
+            }
+
+            public void OnNext(AvaloniaPropertyChangedEventArgs<TValue> value)
+            {
+                if (value.Sender is TTarget target)
+                {
+                    _action(target, value);
+                }
+            }
+        }
+
+        private class ClassHandlerObserver<TTarget> : IObserver<AvaloniaPropertyChangedEventArgs>
+        {
+            private readonly Action<TTarget, AvaloniaPropertyChangedEventArgs> _action;
+
+            public ClassHandlerObserver(Action<TTarget, AvaloniaPropertyChangedEventArgs> action)
+            {
+                _action = action;
+            }
+
+            public void OnCompleted()
+            {
+            }
+
+            public void OnError(Exception error)
+            {
+            }
+
+            public void OnNext(AvaloniaPropertyChangedEventArgs value)
+            {
+                if (value.Sender is TTarget target)
+                {
+                    _action(target, value);
+                }
+            }
+        }
     }
     }
 }
 }

+ 3 - 3
src/Avalonia.Base/AvaloniaProperty`1.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
-using System.Reactive.Subjects;
 using Avalonia.Data;
 using Avalonia.Data;
+using Avalonia.Reactive;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
 namespace Avalonia
 namespace Avalonia
@@ -12,7 +12,7 @@ namespace Avalonia
     /// <typeparam name="TValue">The value type of the property.</typeparam>
     /// <typeparam name="TValue">The value type of the property.</typeparam>
     public abstract class AvaloniaProperty<TValue> : AvaloniaProperty
     public abstract class AvaloniaProperty<TValue> : AvaloniaProperty
     {
     {
-        private readonly Subject<AvaloniaPropertyChangedEventArgs<TValue>> _changed;
+        private readonly LightweightSubject<AvaloniaPropertyChangedEventArgs<TValue>> _changed;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
         /// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
@@ -28,7 +28,7 @@ namespace Avalonia
             Action<AvaloniaObject, bool>? notifying = null)
             Action<AvaloniaObject, bool>? notifying = null)
             : base(name, typeof(TValue), ownerType, metadata, notifying)
             : base(name, typeof(TValue), ownerType, metadata, notifying)
         {
         {
-            _changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>();
+            _changed = new LightweightSubject<AvaloniaPropertyChangedEventArgs<TValue>>();
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 0
src/Avalonia.Base/ClassBindingManager.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Avalonia.Data;
 using Avalonia.Data;
+using Avalonia.Reactive;
 
 
 namespace Avalonia
 namespace Avalonia
 {
 {

+ 1 - 1
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@@ -3,7 +3,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
-using System.Reactive.Disposables;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Collections
 namespace Avalonia.Collections
 {
 {

+ 0 - 1
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@@ -1,6 +1,5 @@
 using System;
 using System;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
-using System.Reactive.Linq;
 using Avalonia.Reactive;
 using Avalonia.Reactive;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 

+ 1 - 1
src/Avalonia.Base/Controls/NameScopeLocator.cs

@@ -1,5 +1,5 @@
 using System;
 using System;
-using System.Reactive.Disposables;
+using Avalonia.Reactive;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls

+ 5 - 6
src/Avalonia.Base/Data/BindingOperations.cs

@@ -1,6 +1,5 @@
 using System;
 using System;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Data
 namespace Avalonia.Data
 {
 {
@@ -46,15 +45,15 @@ namespace Avalonia.Data
                         throw new InvalidOperationException("InstancedBinding does not contain an observable.");
                         throw new InvalidOperationException("InstancedBinding does not contain an observable.");
                     return target.Bind(property, binding.Observable, binding.Priority);
                     return target.Bind(property, binding.Observable, binding.Priority);
                 case BindingMode.TwoWay:
                 case BindingMode.TwoWay:
+                    if (binding.Observable is null)
+                        throw new InvalidOperationException("InstancedBinding does not contain an observable.");
                     if (binding.Subject is null)
                     if (binding.Subject is null)
                         throw new InvalidOperationException("InstancedBinding does not contain a subject.");
                         throw new InvalidOperationException("InstancedBinding does not contain a subject.");
                     return new TwoWayBindingDisposable(
                     return new TwoWayBindingDisposable(
-                        target.Bind(property, binding.Subject, binding.Priority),
+                        target.Bind(property, binding.Observable, binding.Priority),
                         target.GetObservable(property).Subscribe(binding.Subject));
                         target.GetObservable(property).Subscribe(binding.Subject));
                 case BindingMode.OneTime:
                 case BindingMode.OneTime:
-                    var source = binding.Subject ?? binding.Observable;
-
-                    if (source != null)
+                    if (binding.Observable is {} source)
                     {
                     {
                         // Perf: Avoid allocating closure in the outer scope.
                         // Perf: Avoid allocating closure in the outer scope.
                         var targetCopy = target;
                         var targetCopy = target;

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

@@ -38,7 +38,7 @@ namespace Avalonia.Data.Converters
                     }
                     }
                     else if (Equals(obj, default(TIn)))
                     else if (Equals(obj, default(TIn)))
                     {
                     {
-                        yield return default(TIn);
+                        yield return default;
                     }
                     }
                 }
                 }
             }
             }

+ 0 - 1
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@@ -1,5 +1,4 @@
 using System;
 using System;
-using System.Reactive.Linq;
 using Avalonia.Reactive;
 using Avalonia.Reactive;
 
 
 namespace Avalonia.Data.Core
 namespace Avalonia.Data.Core

+ 2 - 4
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -1,11 +1,9 @@
 using System;
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
+using Avalonia.Reactive;
 using Avalonia.Data.Converters;
 using Avalonia.Data.Converters;
 using Avalonia.Logging;
 using Avalonia.Logging;
-using Avalonia.Reactive;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
 namespace Avalonia.Data.Core
 namespace Avalonia.Data.Core
@@ -15,7 +13,7 @@ namespace Avalonia.Data.Core
     /// that are sent and received.
     /// that are sent and received.
     /// </summary>
     /// </summary>
     [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
     [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
-    public class BindingExpression : LightweightObservableBase<object?>, ISubject<object?>, IDescription
+    public class BindingExpression : LightweightObservableBase<object?>, IAvaloniaSubject<object?>, IDescription
     {
     {
         private readonly ExpressionObserver _inner;
         private readonly ExpressionObserver _inner;
         private readonly Type _targetType;
         private readonly Type _targetType;

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

@@ -2,8 +2,6 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq.Expressions;
 using System.Linq.Expressions;
-using System.Reactive;
-using System.Reactive.Linq;
 using Avalonia.Data.Core.Parsers;
 using Avalonia.Data.Core.Parsers;
 using Avalonia.Data.Core.Plugins;
 using Avalonia.Data.Core.Plugins;
 using Avalonia.Reactive;
 using Avalonia.Reactive;
@@ -99,14 +97,14 @@ namespace Avalonia.Data.Core
         /// </summary>
         /// </summary>
         /// <param name="rootGetter">A function which gets the root object.</param>
         /// <param name="rootGetter">A function which gets the root object.</param>
         /// <param name="node">The expression.</param>
         /// <param name="node">The expression.</param>
-        /// <param name="update">An observable which triggers a re-read of the getter.</param>
+        /// <param name="update">An observable which triggers a re-read of the getter. Generic argument value is not used.</param>
         /// <param name="description">
         /// <param name="description">
         /// A description of the expression.
         /// A description of the expression.
         /// </param>
         /// </param>
         public ExpressionObserver(
         public ExpressionObserver(
             Func<object?> rootGetter,
             Func<object?> rootGetter,
             ExpressionNode node,
             ExpressionNode node,
-            IObservable<Unit> update,
+            IObservable<ValueTuple> update,
             string? description)
             string? description)
         {
         {
             Description = description;
             Description = description;
@@ -164,7 +162,7 @@ namespace Avalonia.Data.Core
         /// </summary>
         /// </summary>
         /// <param name="rootGetter">A function which gets the root object.</param>
         /// <param name="rootGetter">A function which gets the root object.</param>
         /// <param name="expression">The expression.</param>
         /// <param name="expression">The expression.</param>
-        /// <param name="update">An observable which triggers a re-read of the getter.</param>
+        /// <param name="update">An observable which triggers a re-read of the getter. Generic argument value is not used.</param>
         /// <param name="enableDataValidation">Whether or not to track data validation</param>
         /// <param name="enableDataValidation">Whether or not to track data validation</param>
         /// <param name="description">
         /// <param name="description">
         /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
         /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
@@ -173,7 +171,7 @@ namespace Avalonia.Data.Core
         public static ExpressionObserver Create<T, U>(
         public static ExpressionObserver Create<T, U>(
             Func<T> rootGetter,
             Func<T> rootGetter,
             Expression<Func<T, U>> expression,
             Expression<Func<T, U>> expression,
-            IObservable<Unit> update,
+            IObservable<ValueTuple> update,
             bool enableDataValidation = false,
             bool enableDataValidation = false,
             string? description = null)
             string? description = null)
         {
         {
@@ -296,9 +294,10 @@ namespace Avalonia.Data.Core
             if (_root is IObservable<object> observable)
             if (_root is IObservable<object> observable)
             {
             {
                 _rootSubscription = observable.Subscribe(
                 _rootSubscription = observable.Subscribe(
-                    x => _node.Target = new WeakReference<object?>(x != AvaloniaProperty.UnsetValue ? x : null),
-                    x => PublishCompleted(),
-                    () => PublishCompleted());
+                    new AnonymousObserver<object>(
+                        x => _node.Target = new WeakReference<object?>(x != AvaloniaProperty.UnsetValue ? x : null),
+                        x => PublishCompleted(),
+                        PublishCompleted));
             }
             }
             else
             else
             {
             {

+ 38 - 23
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@@ -1,48 +1,47 @@
 using System;
 using System;
 using System.Collections;
 using System.Collections;
-using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
-using System.Linq;
-using System.Reactive.Linq;
+using Avalonia.Reactive;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
 namespace Avalonia.Data.Core
 namespace Avalonia.Data.Core
 {
 {
-    public abstract class IndexerNodeBase : SettableNode
+    public abstract class IndexerNodeBase : SettableNode,
+        IWeakEventSubscriber<NotifyCollectionChangedEventArgs>,
+        IWeakEventSubscriber<PropertyChangedEventArgs>
     {
     {
-        private IDisposable? _subscription;
-        
         protected override void StartListeningCore(WeakReference<object?> reference)
         protected override void StartListeningCore(WeakReference<object?> reference)
         {
         {
             reference.TryGetTarget(out var target);
             reference.TryGetTarget(out var target);
 
 
-            var incc = target as INotifyCollectionChanged;
-            var inpc = target as INotifyPropertyChanged;
-            var inputs = new List<IObservable<object?>>();
-
-            if (incc != null)
+            if (target is INotifyCollectionChanged incc)
             {
             {
-                inputs.Add(WeakObservable.FromEventPattern(
-                    incc, WeakEvents.CollectionChanged)
-                    .Where(x => ShouldUpdate(x.Sender, x.EventArgs))
-                    .Select(_ => GetValue(target)));
+                WeakEvents.CollectionChanged.Subscribe(incc, this);
             }
             }
 
 
-            if (inpc != null)
+            if (target is INotifyPropertyChanged inpc)
             {
             {
-                inputs.Add(WeakObservable.FromEventPattern(
-                    inpc, WeakEvents.PropertyChanged)
-                    .Where(x => ShouldUpdate(x.Sender, x.EventArgs))
-                    .Select(_ => GetValue(target)));
+                WeakEvents.PropertyChanged.Subscribe(inpc, this);
             }
             }
-
-            _subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged);
+            
+            ValueChanged(GetValue(target));
         }
         }
 
 
         protected override void StopListeningCore()
         protected override void StopListeningCore()
         {
         {
-            _subscription?.Dispose();
+            if (Target.TryGetTarget(out var target))
+            {
+                if (target is INotifyCollectionChanged incc)
+                {
+                    WeakEvents.CollectionChanged.Unsubscribe(incc, this);
+                }
+
+                if (target is INotifyPropertyChanged inpc)
+                {
+                    WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
+                }
+            }
         }
         }
 
 
         protected abstract object? GetValue(object? target);
         protected abstract object? GetValue(object? target);
@@ -83,5 +82,21 @@ namespace Avalonia.Data.Core
         }
         }
 
 
         protected abstract bool ShouldUpdate(object? sender, PropertyChangedEventArgs e);
         protected abstract bool ShouldUpdate(object? sender, PropertyChangedEventArgs e);
+
+        void IWeakEventSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs e)
+        {
+            if (ShouldUpdate(sender, e))
+            {
+                ValueChanged(GetValue(sender));
+            }
+        }
+
+        void IWeakEventSubscriber<PropertyChangedEventArgs>.OnEvent(object? sender, WeakEvent ev, PropertyChangedEventArgs e)
+        {
+            if (ShouldUpdate(sender, e))
+            {
+                ValueChanged(GetValue(sender));
+            }
+        }
     }
     }
 }
 }

+ 24 - 41
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Linq;
-using System.Reactive.Linq;
+using Avalonia.Reactive;
 using System.Reflection;
 using System.Reflection;
 
 
 namespace Avalonia.Data.Core.Plugins
 namespace Avalonia.Data.Core.Plugins
@@ -12,8 +12,15 @@ namespace Avalonia.Data.Core.Plugins
     [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
     [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
     public class ObservableStreamPlugin : IStreamPlugin
     public class ObservableStreamPlugin : IStreamPlugin
     {
     {
-        static MethodInfo? observableSelect;
+        private static MethodInfo? s_observableGeneric;
+        private static MethodInfo? s_observableSelect;
 
 
+        [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicProperties, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")]
+        public ObservableStreamPlugin()
+        {
+            
+        }
+        
         /// <summary>
         /// <summary>
         /// Checks whether this plugin handles the specified value.
         /// Checks whether this plugin handles the specified value.
         /// </summary>
         /// </summary>
@@ -54,56 +61,32 @@ namespace Avalonia.Data.Core.Plugins
                   x.IsGenericType &&
                   x.IsGenericType &&
                   x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
                   x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
 
 
-            // Get the Observable.Select method.
-            var select = GetObservableSelect(sourceType);
-
-            // Make a Box<> delegate of the correct type.
-            var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object));
-            var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)!
-                .MakeGenericMethod(sourceType)
-                .CreateDelegate(funcType);
+            // Get the BoxObservable<T> method.
+            var select = GetBoxObservable(sourceType);
 
 
-            // Call Observable.Select(target, box);
+            // Call BoxObservable(target);
             return (IObservable<object?>)select.Invoke(
             return (IObservable<object?>)select.Invoke(
                 null,
                 null,
-                new object[] { target, box })!;
+                new[] { target })!;
         }
         }
 
 
         [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
         [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
-        private static MethodInfo GetObservableSelect(Type source)
+        private static MethodInfo GetBoxObservable(Type source)
         {
         {
-            return GetObservableSelect().MakeGenericMethod(source, typeof(object));
+            return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source);
         }
         }
 
 
-        private static MethodInfo GetObservableSelect()
+        [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
+        private static MethodInfo GetBoxObservable()
         {
         {
-            if (observableSelect == null)
-            {
-                observableSelect = typeof(Observable).GetRuntimeMethods().First(x =>
-                {
-                    if (x.Name == nameof(Observable.Select) &&
-                        x.ContainsGenericParameters &&
-                        x.GetGenericArguments().Length == 2)
-                    {
-                        var parameters = x.GetParameters();
-
-                        if (parameters.Length == 2 &&
-                            parameters[0].ParameterType.IsConstructedGenericType &&
-                            parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) &&
-                            parameters[1].ParameterType.IsConstructedGenericType &&
-                            parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
-                        {
-                            return true;
-                        }
-                    }
-
-                    return false;
-                });
-            }
-
-            return observableSelect;
+            return s_observableSelect
+               ??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic)
+               ?? throw new InvalidOperationException("BoxObservable method was not found.");
         }
         }
 
 
-        private static object? Box<T>(T value) => (object?)value;
+        private static IObservable<object?> BoxObservable<T>(IObservable<T> source)
+        {
+            return source.Select(v => (object?)v);
+        }
     }
     }
 }
 }

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

@@ -1,9 +1,8 @@
 using System;
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
 using System.Reflection;
 using System.Reflection;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Data.Core.Plugins
 namespace Avalonia.Data.Core.Plugins
 {
 {
@@ -50,7 +49,7 @@ namespace Avalonia.Data.Core.Plugins
                         case TaskStatus.Faulted:
                         case TaskStatus.Faulted:
                             return HandleCompleted(task);
                             return HandleCompleted(task);
                         default:
                         default:
-                            var subject = new Subject<object?>();
+                            var subject = new LightweightSubject<object?>();
                             task.ContinueWith(
                             task.ContinueWith(
                                     x => HandleCompleted(task).Subscribe(subject),
                                     x => HandleCompleted(task).Subscribe(subject),
                                     TaskScheduler.FromCurrentSynchronizationContext())
                                     TaskScheduler.FromCurrentSynchronizationContext())

+ 1 - 1
src/Avalonia.Base/Data/Core/StreamNode.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
-using System.Reactive.Linq;
 using Avalonia.Data.Core.Plugins;
 using Avalonia.Data.Core.Plugins;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Data.Core
 namespace Avalonia.Data.Core
 {
 {

+ 7 - 2
src/Avalonia.Base/Data/IndexerBinding.cs

@@ -1,4 +1,6 @@
-namespace Avalonia.Data
+using Avalonia.Reactive;
+
+namespace Avalonia.Data
 {
 {
     public class IndexerBinding : IBinding
     public class IndexerBinding : IBinding
     {
     {
@@ -22,7 +24,10 @@
             object? anchor = null,
             object? anchor = null,
             bool enableDataValidation = false)
             bool enableDataValidation = false)
         {
         {
-            return new InstancedBinding(Source.GetSubject(Property), Mode, BindingPriority.LocalValue);
+            var subject = new CombinedSubject<object?>(
+                new AnonymousObserver<object?>(x => Source.SetValue(Property, x, BindingPriority.LocalValue)),
+                Source.GetObservable(Property));
+            return new InstancedBinding(subject, Mode, BindingPriority.LocalValue);
         }
         }
     }
     }
 }
 }

+ 2 - 3
src/Avalonia.Base/Data/IndexerDescriptor.cs

@@ -1,12 +1,11 @@
 using System;
 using System;
-using System.Reactive;
 
 
 namespace Avalonia.Data
 namespace Avalonia.Data
 {
 {
     /// <summary>
     /// <summary>
     /// Holds a description of a binding for <see cref="AvaloniaObject"/>'s [] operator.
     /// Holds a description of a binding for <see cref="AvaloniaObject"/>'s [] operator.
     /// </summary>
     /// </summary>
-    public class IndexerDescriptor : ObservableBase<object?>, IDescription
+    public class IndexerDescriptor : IObservable<object?>, IDescription
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the binding mode.
         /// Gets or sets the binding mode.
@@ -104,7 +103,7 @@ namespace Avalonia.Data
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected override IDisposable SubscribeCore(IObserver<object?> observer)
+        public IDisposable Subscribe(IObserver<object?> observer)
         {
         {
             if (SourceObservable is null && Source is null)
             if (SourceObservable is null && Source is null)
                 throw new InvalidOperationException("Cannot subscribe to IndexerDescriptor.");
                 throw new InvalidOperationException("Cannot subscribe to IndexerDescriptor.");

+ 20 - 30
src/Avalonia.Base/Data/InstancedBinding.cs

@@ -1,5 +1,5 @@
 using System;
 using System;
-using System.Reactive.Subjects;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Data
 namespace Avalonia.Data
 {
 {
@@ -14,26 +14,7 @@ namespace Avalonia.Data
     /// </remarks>
     /// </remarks>
     public class InstancedBinding
     public class InstancedBinding
     {
     {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="InstancedBinding"/> class.
-        /// </summary>
-        /// <param name="subject">The binding source.</param>
-        /// <param name="mode">The binding mode.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <remarks>
-        /// This constructor can be used to create any type of binding and as such requires an
-        /// <see cref="ISubject{Object}"/> as the binding source because this is the only binding
-        /// source which can be used for all binding modes. If you wish to create an instance with
-        /// something other than a subject, use one of the static creation methods on this class.
-        /// </remarks>
-        public InstancedBinding(ISubject<object?> subject, BindingMode mode, BindingPriority priority)
-        {
-            Mode = mode;
-            Priority = priority;
-            Value = subject ?? throw new ArgumentNullException(nameof(subject));
-        }
-
-        private InstancedBinding(object? value, BindingMode mode, BindingPriority priority)
+        internal InstancedBinding(object? value, BindingMode mode, BindingPriority priority)
         {
         {
             Mode = mode;
             Mode = mode;
             Priority = priority;
             Priority = priority;
@@ -61,9 +42,14 @@ namespace Avalonia.Data
         public IObservable<object?>? Observable => Value as IObservable<object?>;
         public IObservable<object?>? Observable => Value as IObservable<object?>;
 
 
         /// <summary>
         /// <summary>
-        /// Gets the <see cref="Value"/> as a subject.
+        /// Gets the <see cref="Value"/> as an observer.
+        /// </summary>
+        public IObserver<object?>? Observer => Value as IObserver<object?>;
+
+        /// <summary>
+        /// Gets the <see cref="Subject"/> as an subject.
         /// </summary>
         /// </summary>
-        public ISubject<object?>? Subject => Value as ISubject<object?>;
+        internal IAvaloniaSubject<object?>? Subject => Value as IAvaloniaSubject<object?>;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new one-time binding with a fixed value.
         /// Creates a new one-time binding with a fixed value.
@@ -111,30 +97,34 @@ namespace Avalonia.Data
         /// <summary>
         /// <summary>
         /// Creates a new one-way to source binding.
         /// Creates a new one-way to source binding.
         /// </summary>
         /// </summary>
-        /// <param name="subject">The binding source.</param>
+        /// <param name="observer">The binding source.</param>
         /// <param name="priority">The priority of the binding.</param>
         /// <param name="priority">The priority of the binding.</param>
         /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
         /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
         public static InstancedBinding OneWayToSource(
         public static InstancedBinding OneWayToSource(
-            ISubject<object?> subject,
+            IObserver<object?> observer,
             BindingPriority priority = BindingPriority.LocalValue)
             BindingPriority priority = BindingPriority.LocalValue)
         {
         {
-            _ = subject ?? throw new ArgumentNullException(nameof(subject));
+            _ = observer ?? throw new ArgumentNullException(nameof(observer));
 
 
-            return new InstancedBinding(subject, BindingMode.OneWayToSource, priority);
+            return new InstancedBinding(observer, BindingMode.OneWayToSource, priority);
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Creates a new two-way binding.
         /// Creates a new two-way binding.
         /// </summary>
         /// </summary>
-        /// <param name="subject">The binding source.</param>
+        /// <param name="observable">The binding source.</param>
+        /// <param name="observer">The binding source.</param>
         /// <param name="priority">The priority of the binding.</param>
         /// <param name="priority">The priority of the binding.</param>
         /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
         /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
         public static InstancedBinding TwoWay(
         public static InstancedBinding TwoWay(
-            ISubject<object?> subject,
+            IObservable<object?> observable,
+            IObserver<object?> observer,
             BindingPriority priority = BindingPriority.LocalValue)
             BindingPriority priority = BindingPriority.LocalValue)
         {
         {
-            _ = subject ?? throw new ArgumentNullException(nameof(subject));
+            _ = observable ?? throw new ArgumentNullException(nameof(observable));
+            _ = observer ?? throw new ArgumentNullException(nameof(observer));
 
 
+            var subject = new CombinedSubject<object?>(observer, observable);
             return new InstancedBinding(subject, BindingMode.TwoWay, priority);
             return new InstancedBinding(subject, BindingMode.TwoWay, priority);
         }
         }
 
 

+ 1 - 2
src/Avalonia.Base/Input/Cursor.cs

@@ -71,8 +71,7 @@ namespace Avalonia.Input
 
 
         private static ICursorFactory GetCursorFactory()
         private static ICursorFactory GetCursorFactory()
         {
         {
-            return AvaloniaLocator.Current.GetService<ICursorFactory>() ??
-                throw new Exception("Could not create Cursor: ICursorFactory not registered.");
+            return AvaloniaLocator.Current.GetRequiredService<ICursorFactory>();
         }
         }
     }
     }
 }
 }

+ 8 - 1
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@@ -189,7 +189,14 @@ namespace Avalonia.Input.GestureRecognizers
 
 
                         var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds);
                         var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds);
                         var distance = speed * elapsedSinceLastTick.TotalSeconds;
                         var distance = speed * elapsedSinceLastTick.TotalSeconds;
-                        _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance));
+                        var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance);
+                        _target!.RaiseEvent(scrollGestureEventArgs);
+
+                        if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture)
+                        {
+                            EndGesture();
+                            return false;
+                        }
 
 
                         // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
                         // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
                         if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
                         if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)

+ 1 - 0
src/Avalonia.Base/Input/Gestures.cs

@@ -3,6 +3,7 @@ using System.Threading;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Threading;
 using Avalonia.Threading;
+using Avalonia.Reactive;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 
 
 namespace Avalonia.Input
 namespace Avalonia.Input

+ 1 - 0
src/Avalonia.Base/Input/InputElement.cs

@@ -7,6 +7,7 @@ using Avalonia.Data;
 using Avalonia.Input.GestureRecognizers;
 using Avalonia.Input.GestureRecognizers;
 using Avalonia.Input.TextInput;
 using Avalonia.Input.TextInput;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
+using Avalonia.Reactive;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 
 
 #nullable enable
 #nullable enable

+ 4 - 4
src/Avalonia.Base/Input/InputManager.cs

@@ -1,6 +1,6 @@
 using System;
 using System;
-using System.Reactive.Subjects;
 using Avalonia.Input.Raw;
 using Avalonia.Input.Raw;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Input
 namespace Avalonia.Input
 {
 {
@@ -10,9 +10,9 @@ namespace Avalonia.Input
     /// </summary>
     /// </summary>
     public class InputManager : IInputManager
     public class InputManager : IInputManager
     {
     {
-        private readonly Subject<RawInputEventArgs> _preProcess = new Subject<RawInputEventArgs>();
-        private readonly Subject<RawInputEventArgs> _process = new Subject<RawInputEventArgs>();
-        private readonly Subject<RawInputEventArgs> _postProcess = new Subject<RawInputEventArgs>();
+        private readonly LightweightSubject<RawInputEventArgs> _preProcess = new();
+        private readonly LightweightSubject<RawInputEventArgs> _process = new();
+        private readonly LightweightSubject<RawInputEventArgs> _postProcess = new();
 
 
         /// <summary>
         /// <summary>
         /// Gets the global instance of the input manager.
         /// Gets the global instance of the input manager.

+ 1 - 0
src/Avalonia.Base/Input/MouseDevice.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using Avalonia.Reactive;
 using Avalonia.Input.Raw;
 using Avalonia.Input.Raw;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Utilities;
 using Avalonia.Utilities;

+ 5 - 0
src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs

@@ -81,6 +81,10 @@ namespace Avalonia.Input.Platform
             {
             {
                 new KeyGesture(Key.Apps)
                 new KeyGesture(Key.Apps)
             };
             };
+            Back = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Left, KeyModifiers.Alt)
+            };
         }
         }
 
 
         public KeyModifiers CommandModifiers { get; set; }
         public KeyModifiers CommandModifiers { get; set; }
@@ -101,5 +105,6 @@ namespace Avalonia.Input.Platform
         public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
         public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
         public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
         public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
         public List<KeyGesture> OpenContextMenu { get; set; }
         public List<KeyGesture> OpenContextMenu { get; set; }
+        public List<KeyGesture> Back { get; set; }
     }
     }
 }
 }

+ 4 - 0
src/Avalonia.Base/Input/ScrollGestureEventArgs.cs

@@ -6,6 +6,10 @@ namespace Avalonia.Input
     {
     {
         public int Id { get; }
         public int Id { get; }
         public Vector Delta { get; }
         public Vector Delta { get; }
+        /// <summary>
+        /// When set the ScrollGestureRecognizer should stop its current active scroll gesture.
+        /// </summary>
+        public bool ShouldEndScrollGesture { get; set; }
         private static int _nextId = 1;
         private static int _nextId = 1;
 
 
         public static int GetNextFreeId() => _nextId++;
         public static int GetNextFreeId() => _nextId++;

+ 1 - 1
src/Avalonia.Base/Input/TextInput/InputMethodManager.cs

@@ -1,5 +1,5 @@
 using System;
 using System;
-using Avalonia.VisualTree;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Input.TextInput
 namespace Avalonia.Input.TextInput
 {
 {

+ 1 - 4
src/Avalonia.Base/Interactivity/EventRoute.cs

@@ -143,10 +143,7 @@ namespace Avalonia.Interactivity
                 // If we've got to a new control then call any RoutedEvent.Raised listeners.
                 // If we've got to a new control then call any RoutedEvent.Raised listeners.
                 if (entry.Target != lastTarget)
                 if (entry.Target != lastTarget)
                 {
                 {
-                    if (!e.Handled)
-                    {
-                        _event.InvokeRaised(entry.Target, e);
-                    }
+                    _event.InvokeRaised(entry.Target, e);
 
 
                     // If this is a direct event and we've already raised events then we're finished.
                     // If this is a direct event and we've already raised events then we're finished.
                     if (e.Route == RoutingStrategies.Direct && lastTarget is object)
                     if (e.Route == RoutingStrategies.Direct && lastTarget is object)

+ 1 - 2
src/Avalonia.Base/Interactivity/InteractiveExtensions.cs

@@ -1,6 +1,5 @@
 using System;
 using System;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
+using Avalonia.Reactive;
 
 
 namespace Avalonia.Interactivity
 namespace Avalonia.Interactivity
 {
 {

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