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>
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
<RunApiCompat>False</RunApiCompat>
<LangVersion>11</LangVersion>
</PropertyGroup>
</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
Copyright © 2008-2013 Kristian Høgsberg
Copyright © 2010-2013 Intel Corporation
Copyright © 2013 Rafael Antognolli
Copyright © 2013 Jasper St. Pierre
Copyright © 2014 Jonas Ådahl
Copyright © 2014 Jason Ekstrand
Copyright © 2014-2015 Collabora, Ltd.
Copyright © 2015 Red Hat Inc.
Copyright © 2008-2013 Kristian Høgsberg
Copyright © 2010-2013 Intel Corporation
Copyright © 2013 Rafael Antognolli
Copyright © 2013 Jasper St. Pierre
Copyright © 2014 Jonas Ådahl
Copyright © 2014 Jason Ekstrand
Copyright © 2014-2015 Collabora, Ltd.
Copyright © 2015 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
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
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
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
//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//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'">
<PackageReference Include="System.ValueTuple" Version="4.5.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" />
</ItemGroup>
</Project>

1
build/SharedVersion.props

@ -8,7 +8,6 @@
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<LangVersion>preview</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<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>

2
global.json

@ -4,6 +4,6 @@
"rollForward": "latestFeature"
},
"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 */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
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 */
/* Begin PBXFileReference section */
@ -103,6 +104,7 @@
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>"; };
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 */
/* Begin PBXFrameworksBuildPhase section */
@ -163,6 +165,7 @@
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */,
AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */,
18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
@ -299,6 +302,7 @@
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

5
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -44,7 +44,7 @@
-(bool) isDialog
{
return _parent->IsDialog();
return _parent->IsModal();
}
-(double) getExtendedTitleBarHeight
@ -281,6 +281,9 @@
- (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
{
if (_parent == nullptr)
return;
_parent->BringToFront();
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 SetFrameThemeVariant(AvnPlatformThemeVariant variant) override;
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
void *sourceHandle) override;
virtual bool IsDialog();
virtual bool IsModal();
id<AvnWindowProtocol> GetWindowProtocol ();

20
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -498,6 +498,24 @@ HRESULT WindowBaseImpl::SetTransparencyMode(AvnWindowTransparencyMode mode) {
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) {
START_COM_CALL;
@ -542,7 +560,7 @@ HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, Av
return S_OK;
}
bool WindowBaseImpl::IsDialog() {
bool WindowBaseImpl::IsModal() {
return false;
}

6
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -23,7 +23,7 @@ private:
NSRect _preZoomSize;
bool _transitioningWindowState;
bool _isClientAreaExtended;
bool _isDialog;
bool _isModal;
WindowImpl* _parent;
std::list<WindowImpl*> _children;
AvnExtendClientAreaChromeHints _extendClientHints;
@ -91,7 +91,9 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT SetWindowState (AvnWindowState state) override;
virtual bool IsDialog() override;
virtual bool IsModal() override;
bool IsOwned();
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;
@autoreleasepool {
_isDialog = isDialog || _parent != nullptr;
_isModal = isDialog;
WindowBaseImpl::Show(activate, isDialog);
@ -97,7 +97,7 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
_parent = cparent;
_isDialog = _parent != nullptr;
_isModal = _parent != 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
@ -123,7 +123,7 @@ void WindowImpl::BringToFront()
{
if ([Window isVisible] && ![Window isMiniaturized])
{
if(IsDialog())
if(IsModal())
{
Activate();
}
@ -150,7 +150,7 @@ bool WindowImpl::CanBecomeKeyWindow()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
if((*iterator)->IsDialog())
if((*iterator)->IsModal())
{
return false;
}
@ -569,8 +569,12 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
}
}
bool WindowImpl::IsDialog() {
return _isDialog;
bool WindowImpl::IsModal() {
return _isModal;
}
bool WindowImpl::IsOwned() {
return _parent != nullptr;
}
NSWindowStyleMask WindowImpl::GetStyle() {
@ -599,7 +603,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
break;
}
if (!IsDialog()) {
if (!IsOwned()) {
s |= NSWindowStyleMaskMiniaturizable;
}

1
native/Avalonia.Native/src/OSX/common.h

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

25
packages/Avalonia/AvaloniaBuildTasks.targets

@ -48,8 +48,12 @@
</PropertyGroup>
<Target Name="_GenerateAvaloniaResourcesDependencyCache" BeforeTargets="GenerateAvaloniaResources">
<PropertyGroup>
<_AvaloniaResourcesInputsCacheFilePath>$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache</_AvaloniaResourcesInputsCacheFilePath>
</PropertyGroup>
<ItemGroup>
<CustomAdditionalGenerateAvaloniaResourcesInputs Include="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" />
<CustomAdditionalGenerateAvaloniaResourcesInputs Include="$(_AvaloniaResourcesInputsCacheFilePath)" />
</ItemGroup>
<Hash ItemsToHash="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)">
@ -57,7 +61,11 @@
</Hash>
<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 Name="GenerateAvaloniaResources"
@ -68,7 +76,7 @@
Condition="('@(AvaloniaResource->Count())' &gt; 0) or ('@(AvaloniaXaml->Count())' &gt; 0)"
>
<ItemGroup>
<AvaloniaResource Include="@(AvaloniaXaml)"/>
<AvaloniaResource Include="@(AvaloniaXaml)" />
</ItemGroup>
<GenerateAvaloniaResourcesTask
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
@ -76,6 +84,9 @@
Root="$(MSBuildProjectDirectory)"
Resources="@(AvaloniaResource)"
ReportImportance="$(AvaloniaXamlReportImportance)"/>
<ItemGroup Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'">
<FileWrites Include="$(AvaloniaResourcesTemporaryFilePath)" />
</ItemGroup>
<Exec
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"/>
@ -103,6 +114,9 @@
File="$(AvaloniaXamlReferencesTemporaryFilePath)"
Lines="@(ReferencePathWithRefAssemblies)"
Overwrite="true" />
<ItemGroup Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'">
<FileWrites Include="$(AvaloniaXamlReferencesTemporaryFilePath)" />
</ItemGroup>
<CompileAvaloniaXamlTask
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
AssemblyFile="@(IntermediateAssembly)"
@ -117,8 +131,9 @@
DelaySign="$(DelaySign)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
/>
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)">
<Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
</CompileAvaloniaXamlTask>
<Exec
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"/>

2
samples/ControlCatalog.Desktop/Program.cs

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

73
samples/ControlCatalog/MainView.xaml.cs

@ -3,9 +3,11 @@ using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@ -14,10 +16,14 @@ namespace ControlCatalog
{
public class MainView : UserControl
{
private readonly IPlatformSettings _platformSettings;
public MainView()
{
AvaloniaXamlLoader.Load(this);
_platformSettings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
var sideBar = this.Get<TabControl>("Sidebar");
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
@ -37,6 +43,15 @@ namespace ControlCatalog
if (themes.SelectedItem is CatalogTheme 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");
if (VisualRoot is Window window)
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 Capital { get; private set; }
public StateData(string name, string abbreviatoin, string capital)
public StateData(string name, string abbreviation, string capital)
{
Name = name;
Abbreviation = abbreviatoin;
Abbreviation = abbreviation;
Capital = capital;
}
@ -17,4 +17,4 @@ public class StateData
{
return Name;
}
}
}

4
samples/ControlCatalog/Pages/CarouselPage.xaml

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

2
samples/ControlCatalog/Pages/DialogsPage.xaml

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

64
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

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

1
samples/ControlCatalog/Pages/MenuPage.xaml.cs

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

2
samples/ControlCatalog/Pages/NotificationsPage.xaml.cs

@ -27,7 +27,7 @@ namespace ControlCatalog.Pages
{
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
{
public IBrush Brush;
public IBrush? Brush;
public Point Point;
public double Radius;
public double? Pressure;

1
samples/ControlCatalog/Pages/PointerContactsTab.cs

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

58
samples/ControlCatalog/Pages/ScreenPage.cs

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

2
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -53,7 +53,7 @@
<TabControl
Items="{Binding Tabs}"
Margin="0 16"
HeaderDisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
DisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
TabStripPlacement="{Binding TabPlacement}">
<TabControl.ContentTemplate>
<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)
{
var assets = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
return new Bitmap(assets.Open(new Uri(uri)));
}
}

2
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -38,7 +38,7 @@
UseFloatingWatermark="True"
PasswordChar="*"
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="Right aligned text" TextAlignment="Right" />
<TextBox Width="200" Text="Custom selection brush"

1
samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs

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

8
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.VisualTree;
@ -56,12 +55,9 @@ namespace ControlCatalog.ViewModels
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))
.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 bitmap = new Bitmap(s);
CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));

1
samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs

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

2
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

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

2
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
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;
namespace ControlCatalog.ViewModels

1
samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs

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

2
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

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

1
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

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

5
samples/Directory.Build.props

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

4
samples/MiniMvvm/MiniMvvm.csproj

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

14
samples/MiniMvvm/PropertyChangedExtensions.cs

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

1
samples/MiniMvvm/ViewModelBase.cs

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

1
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

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

3
samples/Previewer/Previewer.csproj

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

1
samples/ReactiveUIDemo/ReactiveUIDemo.csproj

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

4
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

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

5
samples/VirtualizationDemo/MainWindow.xaml

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

10
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -21,7 +21,6 @@ namespace VirtualizationDemo.ViewModels
private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
private Orientation _orientation = Orientation.Vertical;
private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
public MainWindowViewModel()
{
@ -81,15 +80,6 @@ namespace VirtualizationDemo.ViewModels
public IEnumerable<ScrollBarVisibility> ScrollBarVisibilities =>
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 RecreateCommand { get; private set; }
public MiniCommand RemoveItemCommand { get; private set; }

2
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

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

2
src/Android/Avalonia.Android/AndroidPlatform.cs

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

2
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

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

19
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -1,18 +1,14 @@
using System;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
using AndroidRect = Android.Graphics.Rect;
namespace Avalonia.Android
{
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
{
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()

16
src/Android/Avalonia.Android/AvaloniaView.cs

@ -1,11 +1,14 @@
using System;
using Android.Content;
using Android.Content.Res;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Android.Platform;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Android
@ -26,6 +29,7 @@ namespace Avalonia.Android
_root.Prepare();
this.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
OnConfigurationChanged();
}
internal TopLevelImpl TopLevelImpl => _view;
@ -70,6 +74,18 @@ namespace Avalonia.Android
_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
{

2
src/Android/Avalonia.Android/ChoreographerTimer.cs

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Android.OS;
using Android.Views;
using Avalonia.Reactive;
using Avalonia.Rendering;
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 Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
ITopLevelWithSystemNavigationManager
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
@ -57,6 +59,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService);
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -286,6 +290,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
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);
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
@ -300,6 +309,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IStorageProvider StorageProvider { get; }
public ISystemNavigationManager SystemNavigationManager { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
if (TransparencyLevel != transparencyLevel)

3
src/Avalonia.Base/Animation/Animation.cs

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

4
src/Avalonia.Base/Animation/AnimationInstance`1.cs

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

2
src/Avalonia.Base/Animation/AnimatorKeyFrame.cs

@ -63,7 +63,7 @@ namespace Avalonia.Animation
}
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.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
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).
using System;
using System.Reactive.Disposables;
using Avalonia.Reactive;
using Avalonia.Logging;
using Avalonia.Media;

2
src/Avalonia.Base/Animation/Animators/TransformAnimator.cs

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

1
src/Avalonia.Base/Animation/Clock.cs

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

4
src/Avalonia.Base/Animation/CrossFade.cs

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

2
src/Avalonia.Base/Animation/PageSlide.cs

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

2
src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs

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

12
src/Avalonia.Base/Avalonia.Base.csproj

@ -14,7 +14,6 @@
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\System.Memory.props" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
@ -37,6 +36,13 @@
<InternalsVisibleTo Include="Avalonia.Skia, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.ColorPicker, 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.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Direct2D1.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
@ -48,8 +54,12 @@
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, 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.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="MiniMvvm, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="ControlCatalog, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup>

2
src/Avalonia.Base/AvaloniaObject.cs

@ -621,7 +621,7 @@ namespace Avalonia
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property 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,
Optional<T> oldValue,
BindingValue<T> newValue,

176
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -1,10 +1,6 @@
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.Data;
namespace Avalonia
{
@ -127,108 +123,6 @@ namespace Avalonia
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>
/// Binds an <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
@ -407,13 +301,7 @@ namespace Avalonia
Action<TTarget, AvaloniaPropertyChangedEventArgs> action)
where TTarget : AvaloniaObject
{
return observable.Subscribe(e =>
{
if (e.Sender is TTarget target)
{
action(target, e);
}
});
return observable.Subscribe(new ClassHandlerObserver<TTarget>(action));
}
/// <summary>
@ -431,13 +319,7 @@ namespace Avalonia
this IObservable<AvaloniaPropertyChangedEventArgs<TValue>> observable,
Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action) where TTarget : AvaloniaObject
{
return observable.Subscribe(e =>
{
if (e.Sender is TTarget target)
{
action(target, e);
}
});
return observable.Subscribe(new ClassHandlerObserver<TTarget, TValue>(action));
}
private class BindingAdaptor : IBinding
@ -458,5 +340,57 @@ namespace Avalonia
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.Diagnostics.CodeAnalysis;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia
@ -12,7 +12,7 @@ namespace Avalonia
/// <typeparam name="TValue">The value type of the property.</typeparam>
public abstract class AvaloniaProperty<TValue> : AvaloniaProperty
{
private readonly Subject<AvaloniaPropertyChangedEventArgs<TValue>> _changed;
private readonly LightweightSubject<AvaloniaPropertyChangedEventArgs<TValue>> _changed;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty{TValue}"/> class.
@ -28,7 +28,7 @@ namespace Avalonia
Action<AvaloniaObject, bool>? notifying = null)
: base(name, typeof(TValue), ownerType, metadata, notifying)
{
_changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>();
_changed = new LightweightSubject<AvaloniaPropertyChangedEventArgs<TValue>>();
}
/// <summary>

1
src/Avalonia.Base/ClassBindingManager.cs

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

2
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

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

1
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

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

2
src/Avalonia.Base/Controls/NameScopeLocator.cs

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

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

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

1
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

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

6
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -1,11 +1,9 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Reactive;
using Avalonia.Data.Converters;
using Avalonia.Logging;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Data.Core
@ -15,7 +13,7 @@ namespace Avalonia.Data.Core
/// that are sent and received.
/// </summary>
[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 Type _targetType;

17
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Data.Core.Parsers;
using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;
@ -99,14 +97,14 @@ namespace Avalonia.Data.Core
/// </summary>
/// <param name="rootGetter">A function which gets the root object.</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">
/// A description of the expression.
/// </param>
public ExpressionObserver(
Func<object?> rootGetter,
ExpressionNode node,
IObservable<Unit> update,
IObservable<ValueTuple> update,
string? description)
{
Description = description;
@ -164,7 +162,7 @@ namespace Avalonia.Data.Core
/// </summary>
/// <param name="rootGetter">A function which gets the root object.</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="description">
/// 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>(
Func<T> rootGetter,
Expression<Func<T, U>> expression,
IObservable<Unit> update,
IObservable<ValueTuple> update,
bool enableDataValidation = false,
string? description = null)
{
@ -296,9 +294,10 @@ namespace Avalonia.Data.Core
if (_root is IObservable<object> observable)
{
_rootSubscription = observable.Subscribe(
x => _node.Target = new WeakReference<object?>(x != AvaloniaProperty.UnsetValue ? x : null),
x => PublishCompleted(),
() => PublishCompleted());
new AnonymousObserver<object>(
x => _node.Target = new WeakReference<object?>(x != AvaloniaProperty.UnsetValue ? x : null),
x => PublishCompleted(),
PublishCompleted));
}
else
{

61
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -1,48 +1,47 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Reactive;
using Avalonia.Utilities;
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)
{
reference.TryGetTarget(out var target);
var incc = target as INotifyCollectionChanged;
var inpc = target as INotifyPropertyChanged;
var inputs = new List<IObservable<object?>>();
if (incc != null)
if (target is INotifyCollectionChanged incc)
{
inputs.Add(WeakObservable.FromEventPattern(
incc, WeakEvents.CollectionChanged)
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
WeakEvents.CollectionChanged.Subscribe(incc, this);
}
if (inpc != null)
if (target is INotifyPropertyChanged inpc)
{
inputs.Add(WeakObservable.FromEventPattern(
inpc, WeakEvents.PropertyChanged)
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
WeakEvents.PropertyChanged.Subscribe(inpc, this);
}
_subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged);
ValueChanged(GetValue(target));
}
protected override void StopListeningCore()
{
_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);
@ -83,5 +82,21 @@ namespace Avalonia.Data.Core
}
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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Reactive;
using System.Reflection;
namespace Avalonia.Data.Core.Plugins
@ -12,8 +12,15 @@ namespace Avalonia.Data.Core.Plugins
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
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>
/// Checks whether this plugin handles the specified value.
/// </summary>
@ -54,56 +61,32 @@ namespace Avalonia.Data.Core.Plugins
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
// Get the Observable.Select method.
var select = GetObservableSelect(sourceType);
// Make a Box<> delegate of the correct type.
var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object));
var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)!
.MakeGenericMethod(sourceType)
.CreateDelegate(funcType);
// Get the BoxObservable<T> method.
var select = GetBoxObservable(sourceType);
// Call Observable.Select(target, box);
// Call BoxObservable(target);
return (IObservable<object?>)select.Invoke(
null,
new object[] { target, box })!;
new[] { target })!;
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static MethodInfo GetObservableSelect(Type source)
private static MethodInfo GetBoxObservable(Type source)
{
return GetObservableSelect().MakeGenericMethod(source, typeof(object));
return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source);
}
private static MethodInfo GetObservableSelect()
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static MethodInfo GetBoxObservable()
{
if (observableSelect == null)
{
observableSelect = typeof(Observable).GetRuntimeMethods().First(x =>
{
if (x.Name == nameof(Observable.Select) &&
x.ContainsGenericParameters &&
x.GetGenericArguments().Length == 2)
{
var parameters = x.GetParameters();
if (parameters.Length == 2 &&
parameters[0].ParameterType.IsConstructedGenericType &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) &&
parameters[1].ParameterType.IsConstructedGenericType &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
{
return true;
}
}
return false;
});
}
return observableSelect;
return s_observableSelect
??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic)
?? throw new InvalidOperationException("BoxObservable method was not found.");
}
private static object? Box<T>(T value) => (object?)value;
private static IObservable<object?> BoxObservable<T>(IObservable<T> source)
{
return source.Select(v => (object?)v);
}
}
}

5
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

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

2
src/Avalonia.Base/Data/Core/StreamNode.cs

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

50
src/Avalonia.Base/Data/InstancedBinding.cs

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

3
src/Avalonia.Base/Input/Cursor.cs

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

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 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
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.Platform;
using Avalonia.Threading;
using Avalonia.Reactive;
using Avalonia.VisualTree;
namespace Avalonia.Input

1
src/Avalonia.Base/Input/InputElement.cs

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

8
src/Avalonia.Base/Input/InputManager.cs

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

1
src/Avalonia.Base/Input/MouseDevice.cs

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

5
src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs

@ -81,6 +81,10 @@ namespace Avalonia.Input.Platform
{
new KeyGesture(Key.Apps)
};
Back = new List<KeyGesture>
{
new KeyGesture(Key.Left, KeyModifiers.Alt)
};
}
public KeyModifiers CommandModifiers { get; set; }
@ -101,5 +105,6 @@ namespace Avalonia.Input.Platform
public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { 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 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;
public static int GetNextFreeId() => _nextId++;

2
src/Avalonia.Base/Input/TextInput/InputMethodManager.cs

@ -1,5 +1,5 @@
using System;
using Avalonia.VisualTree;
using Avalonia.Reactive;
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 (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 (e.Route == RoutingStrategies.Direct && lastTarget is object)

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

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

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

Loading…
Cancel
Save