Browse Source

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

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

1
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>

46
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 © 2008-2013 Kristian Høgsberg
Copyright © 2010-2013 Intel Corporation Copyright © 2010-2013 Intel Corporation
Copyright © 2013 Rafael Antognolli Copyright © 2013 Rafael Antognolli
Copyright © 2013 Jasper St. Pierre Copyright © 2013 Jasper St. Pierre
Copyright © 2014 Jonas Ådahl Copyright © 2014 Jonas Ådahl
Copyright © 2014 Jason Ekstrand Copyright © 2014 Jason Ekstrand
Copyright © 2014-2015 Collabora, Ltd. Copyright © 2014-2015 Collabora, Ltd.
Copyright © 2015 Red Hat Inc. 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
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>

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>

2
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
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;
}; };

5
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
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();
}

4
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 ();

20
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;
} }

6
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;

18
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() { bool WindowImpl::IsModal() {
return _isDialog; 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
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
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()

25
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"/>

2
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);
} }
} }

73
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);
}
} }
} }
} }

6
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;
} }
} }

4
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>

2
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"

64
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> return new List<FileDialogFilter>
{ {
new FileDialogFilter new FileDialogFilter
{ {
Name = "Text files (.txt)", Extensions = new List<string> {"txt"} Name = "Text files (.txt)", Extensions = new List<string> {"txt"}
}, },
new FileDialogFilter new FileDialogFilter
{ {
Name = "All files", Name = "All files",
Extensions = new List<string> {"*"} 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.All,
FilePickerFileTypes.TextPlain 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 stream = await file.OpenWriteAsync();
using var reader = new System.IO.StreamWriter(stream); 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} DateCreated: {props.DateCreated}
DateModified: {props.DateModified} DateModified: {props.DateModified}
CanBookmark: {item.CanBookmark} CanBookmark: {item.CanBookmark}
"; ";
if (item is IStorageFile file) if (item is IStorageFile file)
{ {
resultText += @$" resultText += @$"
CanOpenRead: {file.CanOpenRead} CanOpenRead: {file.CanOpenRead}
CanOpenWrite: {file.CanOpenWrite} CanOpenWrite: {file.CanOpenWrite}
Content: 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"); Window GetWindow() => TopLevel.GetTopLevel(this) as Window ?? throw new NullReferenceException("Invalid Owner");
TopLevel GetTopLevel() => this.VisualRoot as TopLevel ?? throw new NullReferenceException("Invalid Owner"); TopLevel GetTopLevel() => TopLevel.GetTopLevel(this) ?? throw new NullReferenceException("Invalid Owner");
private void InitializeComponent() private void InitializeComponent()
{ {

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;

2
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));
} }
} }
} }

2
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;

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;

58
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);
_leftMost = screen.Bounds.X / 10f; return;
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, Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f,
screen.Bounds.Height / 10f); screen.Bounds.Height / 10f);
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f, Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
screen.WorkingArea.Height / 10f); screen.WorkingArea.Height / 10f);
context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect);
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 = var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height));
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%"); formattedText =
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); 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 = context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
CreateFormattedText(
$"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"); formattedText =
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80)); 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));
} }

2
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">

2
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)));
} }
} }

2
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"

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;

8
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}");
}
} }
} }

2
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));

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;

2
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;

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;

3
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

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;

2
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[]
{ {

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;

5
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>

4
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>

14
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(v1), .CombineLatest(
model.WhenAnyValue(v2), model.WhenAnyValue(v2),
model.WhenAnyValue(v3), (l, r) => (l, r))
cb); .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,

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
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>

3
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>

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>

4
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" HorizontalAlignment="Stretch">
ItemTemplate="{TemplateBinding ItemTemplate}"
Items="{TemplateBinding Items}">
<ItemsPresenter.ItemsPanel> <ItemsPresenter.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel x:Name="HamburgerItemsPanel" <StackPanel x:Name="HamburgerItemsPanel"

5
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>

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; }

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>

2
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())

2
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;

19
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
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
{ {

2
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
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
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
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;
}
}
}

13
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)

3
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 Avalonia.Reactive;
using System.Reactive.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;

4
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
{ {

2
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);
} }
} }

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;

2
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;

2
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
src/Avalonia.Base/Animation/Clock.cs

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

4
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)
{ {

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 =
{ {

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),

12
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>

2
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,

176
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 => return observable.Subscribe(new ClassHandlerObserver<TTarget>(action));
{
if (e.Sender is TTarget target)
{
action(target, e);
}
});
} }
/// <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 => return observable.Subscribe(new ClassHandlerObserver<TTarget, TValue>(action));
{
if (e.Sender is TTarget target)
{
action(target, e);
}
});
} }
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);
}
}
}
} }
} }

6
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
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
{ {

2
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
{ {

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;

2
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

11
src/Avalonia.Base/Data/BindingOperations.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Reactive.Disposables; using Avalonia.Reactive;
using System.Reactive.Linq;
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 (binding.Observable is {} source)
if (source != null)
{ {
// Perf: Avoid allocating closure in the outer scope. // Perf: Avoid allocating closure in the outer scope.
var targetCopy = target; var targetCopy = target;

2
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;
} }
} }
} }

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

6
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 Avalonia.Reactive;
using System.Reactive.Subjects;
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;

17
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), new AnonymousObserver<object>(
x => PublishCompleted(), x => _node.Target = new WeakReference<object?>(x != AvaloniaProperty.UnsetValue ? x : null),
() => PublishCompleted()); x => PublishCompleted(),
PublishCompleted));
} }
else else
{ {

61
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 Avalonia.Reactive;
using System.Reactive.Linq;
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; if (target is INotifyCollectionChanged incc)
var inpc = target as INotifyPropertyChanged;
var inputs = new List<IObservable<object?>>();
if (incc != null)
{ {
inputs.Add(WeakObservable.FromEventPattern( WeakEvents.CollectionChanged.Subscribe(incc, this);
incc, WeakEvents.CollectionChanged)
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
} }
if (inpc != null) if (target is INotifyPropertyChanged inpc)
{ {
inputs.Add(WeakObservable.FromEventPattern( WeakEvents.PropertyChanged.Subscribe(inpc, this);
inpc, WeakEvents.PropertyChanged)
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
} }
_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));
}
}
} }
} }

65
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. // Get the BoxObservable<T> method.
var select = GetObservableSelect(sourceType); var select = GetBoxObservable(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);
// 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) return s_observableSelect
{ ??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic)
observableSelect = typeof(Observable).GetRuntimeMethods().First(x => ?? throw new InvalidOperationException("BoxObservable method was not found.");
{
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;
} }
private static object? Box<T>(T value) => (object?)value; private static IObservable<object?> BoxObservable<T>(IObservable<T> source)
{
return source.Select(v => (object?)v);
}
} }
} }

5
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())

2
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
{ {

9
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);
} }
} }
} }

5
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.");

50
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> internal InstancedBinding(object? value, BindingMode mode, BindingPriority priority)
/// 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)
{ {
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);
} }

3
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>() ?? return AvaloniaLocator.Current.GetRequiredService<ICursorFactory>();
throw new Exception("Could not create Cursor: ICursorFactory not registered.");
} }
} }
} }

9
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
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
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

8
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 LightweightSubject<RawInputEventArgs> _preProcess = new();
private readonly Subject<RawInputEventArgs> _process = new Subject<RawInputEventArgs>(); private readonly LightweightSubject<RawInputEventArgs> _process = new();
private readonly Subject<RawInputEventArgs> _postProcess = new Subject<RawInputEventArgs>(); 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
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
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
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++;

2
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
{ {

5
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)

3
src/Avalonia.Base/Interactivity/InteractiveExtensions.cs

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save