Browse Source

Merge branch 'master' into GeometryConverer

pull/9859/head
Dong Bin 3 years ago
committed by GitHub
parent
commit
565353bfca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .editorconfig
  2. 1
      Directory.Build.props
  3. 77
      NOTICE.md
  4. 16
      azure-pipelines-integrationtests.yml
  5. 24
      azure-pipelines.yml
  6. 3
      build/Base.props
  7. 1
      build/SharedVersion.props
  8. 2
      build/System.Memory.props
  9. 6
      global.json
  10. 2
      samples/ControlCatalog.Android/MainActivity.cs
  11. 4
      samples/ControlCatalog.Android/Resources/values/styles.xml
  12. 2
      samples/ControlCatalog.Android/SplashActivity.cs
  13. 13
      samples/ControlCatalog.Browser/Properties/launchSettings.json
  14. 2
      samples/ControlCatalog.Desktop/Program.cs
  15. 3
      samples/ControlCatalog/MainView.xaml
  16. 23
      samples/ControlCatalog/MainView.xaml.cs
  17. 1
      samples/ControlCatalog/MainWindow.xaml
  18. 2
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  19. 1
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  20. 2
      samples/ControlCatalog/Pages/PointerCanvas.cs
  21. 1
      samples/ControlCatalog/Pages/PointerContactsTab.cs
  22. 58
      samples/ControlCatalog/Pages/ScreenPage.cs
  23. 2
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  24. 2
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  25. 7
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  26. 1
      samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
  27. 8
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  28. 2
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  29. 1
      samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
  30. 11
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  31. 2
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  32. 3
      samples/ControlCatalog/ViewModels/NotificationViewModel.cs
  33. 1
      samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs
  34. 2
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  35. 1
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  36. 5
      samples/Directory.Build.props
  37. 4
      samples/MiniMvvm/MiniMvvm.csproj
  38. 14
      samples/MiniMvvm/PropertyChangedExtensions.cs
  39. 1
      samples/MiniMvvm/ViewModelBase.cs
  40. 1
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  41. 3
      samples/Previewer/Previewer.csproj
  42. 1
      samples/ReactiveUIDemo/ReactiveUIDemo.csproj
  43. 2
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  44. 2
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  45. 14
      src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
  46. 2
      src/Android/Avalonia.Android/AvaloniaView.cs
  47. 2
      src/Android/Avalonia.Android/ChoreographerTimer.cs
  48. 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  49. 85
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  50. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  51. 3
      src/Avalonia.Base/Animation/Animation.cs
  52. 4
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  53. 2
      src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
  54. 2
      src/Avalonia.Base/Animation/Animators/Animator`1.cs
  55. 2
      src/Avalonia.Base/Animation/Animators/ColorAnimator.cs
  56. 2
      src/Avalonia.Base/Animation/Animators/TransformAnimator.cs
  57. 1
      src/Avalonia.Base/Animation/Clock.cs
  58. 4
      src/Avalonia.Base/Animation/CrossFade.cs
  59. 12
      src/Avalonia.Base/Avalonia.Base.csproj
  60. 2
      src/Avalonia.Base/AvaloniaObject.cs
  61. 176
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  62. 6
      src/Avalonia.Base/AvaloniaProperty`1.cs
  63. 1
      src/Avalonia.Base/ClassBindingManager.cs
  64. 2
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  65. 1
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  66. 21
      src/Avalonia.Base/Controls/NameScopeExtensions.cs
  67. 2
      src/Avalonia.Base/Controls/NameScopeLocator.cs
  68. 11
      src/Avalonia.Base/Data/BindingOperations.cs
  69. 2
      src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs
  70. 1
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  71. 6
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  72. 17
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  73. 61
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  74. 65
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  75. 5
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  76. 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  77. 9
      src/Avalonia.Base/Data/IndexerBinding.cs
  78. 5
      src/Avalonia.Base/Data/IndexerDescriptor.cs
  79. 50
      src/Avalonia.Base/Data/InstancedBinding.cs
  80. 3
      src/Avalonia.Base/Input/Cursor.cs
  81. 43
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  82. 375
      src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs
  83. 1
      src/Avalonia.Base/Input/Gestures.cs
  84. 1
      src/Avalonia.Base/Input/InputElement.cs
  85. 8
      src/Avalonia.Base/Input/InputManager.cs
  86. 1
      src/Avalonia.Base/Input/MouseDevice.cs
  87. 2
      src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
  88. 3
      src/Avalonia.Base/Interactivity/InteractiveExtensions.cs
  89. 6
      src/Avalonia.Base/Interactivity/RoutedEvent.cs
  90. 18
      src/Avalonia.Base/Layout/Layoutable.cs
  91. 2
      src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs
  92. 9
      src/Avalonia.Base/Media/Brush.cs
  93. 11
      src/Avalonia.Base/Media/DashStyle.cs
  94. 2
      src/Avalonia.Base/Media/DrawingContext.cs
  95. 4
      src/Avalonia.Base/Media/DrawingGroup.cs
  96. 9
      src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs
  97. 5
      src/Avalonia.Base/Media/FontManager.cs
  98. 5
      src/Avalonia.Base/Media/Geometry.cs
  99. 1
      src/Avalonia.Base/Media/GradientBrush.cs
  100. 2
      src/Avalonia.Base/Media/ImmediateDrawingContext.cs

2
.editorconfig

@ -169,6 +169,8 @@ dotnet_diagnostic.CA1828.severity = warning
dotnet_diagnostic.CA1829.severity = warning
#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
dotnet_diagnostic.CA1847.severity = warning
#CACA2211:Non-constant fields should not be visible
dotnet_diagnostic.CA2211.severity = error
# Wrapping preferences
csharp_wrap_before_ternary_opsigns = false

1
Directory.Build.props

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

77
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
@ -303,3 +303,62 @@ https://github.com/chromium/chromium
// 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.
# Flutter
https://github.com/flutter/flutter
//Copyright 2014 The Flutter Authors. All rights reserved.
//Redistribution and use in source and binary forms, with or without modification,
//are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS;
//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//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.

16
azure-pipelines-integrationtests.yml

@ -13,14 +13,14 @@ jobs:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- script: system_profiler SPDisplaysDataType |grep Resolution
@ -51,14 +51,14 @@ jobs:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: Windows Application Driver@0
inputs:

24
azure-pipelines.yml

@ -30,14 +30,14 @@ jobs:
vmImage: 'ubuntu-20.04'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: CmdLine@2
displayName: 'Install Workloads'
@ -67,14 +67,14 @@ jobs:
vmImage: 'macos-12'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: CmdLine@2
displayName: 'Install Workloads'
@ -138,14 +138,14 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: CmdLine@2
displayName: 'Install Workloads'

3
build/Base.props

@ -1,6 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net6'">
<PackageReference Include="System.ValueTuple" Version="4.5.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
build/System.Memory.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net6'">
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>
</Project>

6
global.json

@ -1,11 +1,9 @@
{
"sdk": {
"version": "7.0.100",
"version": "7.0.101",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43",
"MSBuild.Sdk.Extras": "3.0.22",
"AggregatePackage.NuGet.Sdk" : "0.1.12"
"Microsoft.Build.Traversal": "3.2.0"
}
}

2
samples/ControlCatalog.Android/MainActivity.cs

@ -5,7 +5,7 @@ using Avalonia.Android;
namespace ControlCatalog.Android
{
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaMainActivity
{
}

4
samples/ControlCatalog.Android/Resources/values/styles.xml

@ -14,4 +14,8 @@
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="MyTheme.Main" parent ="MyTheme.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>

2
samples/ControlCatalog.Android/SplashActivity.cs

@ -28,6 +28,8 @@ namespace ControlCatalog.Android
base.OnResume();
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
Finish();
}
}
}

13
samples/ControlCatalog.Browser/Properties/launchSettings.json

@ -0,0 +1,13 @@
{
"profiles": {
"ControlCatalog.Browser": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}"
}
}
}

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

3
samples/ControlCatalog/MainView.xaml

@ -209,8 +209,7 @@
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="TransparencyLevels"
HorizontalAlignment="Stretch"
SelectedIndex="{Binding TransparencyLevel}">
HorizontalAlignment="Stretch">
<ComboBox.Items>
<WindowTransparencyLevel>None</WindowTransparencyLevel>
<WindowTransparencyLevel>Transparent</WindowTransparencyLevel>

23
samples/ControlCatalog/MainView.xaml.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.VisualTree;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@ -59,17 +60,25 @@ namespace ControlCatalog
};
var transparencyLevels = this.Get<ComboBox>("TransparencyLevels");
IDisposable? backgroundSetter = null, paneBackgroundSetter = null;
IDisposable? topLevelBackgroundSideSetter = null, sideBarBackgroundSetter = null, paneBackgroundSetter = null;
transparencyLevels.SelectionChanged += (sender, e) =>
{
backgroundSetter?.Dispose();
topLevelBackgroundSideSetter?.Dispose();
sideBarBackgroundSetter?.Dispose();
paneBackgroundSetter?.Dispose();
if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected
&& selected != WindowTransparencyLevel.None)
if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected)
{
var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.5);
backgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style);
paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style);
var topLevel = (TopLevel)this.GetVisualRoot()!;
topLevel.TransparencyLevelHint = selected;
if (selected != WindowTransparencyLevel.None)
{
var transparentBrush = new ImmutableSolidColorBrush(Colors.White, 0);
var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.2);
topLevelBackgroundSideSetter = topLevel.SetValue(BackgroundProperty, transparentBrush, Avalonia.Data.BindingPriority.Style);
sideBarBackgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style);
paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style);
}
}
};
}

1
samples/ControlCatalog/MainWindow.xaml

@ -10,7 +10,6 @@
ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}"
ExtendClientAreaChromeHints="{Binding ChromeHints}"
ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
TransparencyLevelHint="{Binding TransparencyLevel}"
x:Name="MainWindow"
Background="Transparent"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"

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

@ -17,7 +17,7 @@ namespace ControlCatalog.Pages
this.Get<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";
"12 - hour or 24 - hour clock and visibility of AM / PM is dynamically set based on user time settings, or can be overridden.";
}

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

7
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@ -11,12 +11,5 @@
<CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" />
<CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" />
<Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" />
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>
<ComboBoxItem>AcrylicBlur</ComboBoxItem>
<ComboBoxItem>Mica</ComboBoxItem>
</ComboBox>
</StackPanel>
</UserControl>

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;

11
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;
@ -12,12 +12,9 @@ namespace ControlCatalog.ViewModels
{
class MainWindowViewModel : ViewModelBase
{
private IManagedNotificationManager _notificationManager;
private bool _isMenuItemChecked = true;
private WindowState _windowState;
private WindowState[] _windowStates = Array.Empty<WindowState>();
private int _transparencyLevel;
private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
private bool _extendClientAreaEnabled;
private bool _systemTitleBarEnabled;
@ -77,12 +74,6 @@ namespace ControlCatalog.ViewModels
TitleBarHeight = -1;
}
public int TransparencyLevel
{
get { return _transparencyLevel; }
set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); }
}
public ExtendClientAreaChromeHints ChromeHints
{
get { return _chromeHints; }

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>

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

14
src/Android/Avalonia.Android/AvaloniaSplashActivity.cs

@ -1,6 +1,5 @@
using Android.OS;
using AndroidX.AppCompat.App;
using Avalonia.Controls;
namespace Avalonia.Android
{
@ -8,15 +7,22 @@ namespace Avalonia.Android
{
protected abstract AppBuilder CreateAppBuilder();
private static AppBuilder s_appBuilder;
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
var builder = CreateAppBuilder();
if (s_appBuilder == null)
{
var builder = CreateAppBuilder();
var lifetime = new SingleViewLifetime();
var lifetime = new SingleViewLifetime();
builder.SetupWithLifetime(lifetime);
builder.SetupWithLifetime(lifetime);
s_appBuilder = builder;
}
}
}

2
src/Android/Avalonia.Android/AvaloniaView.cs

@ -24,6 +24,8 @@ namespace Avalonia.Android
_root = new EmbeddableControlRoot(_view);
_root.Prepare();
this.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
}
internal TopLevelImpl TopLevelImpl => _view;

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;

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

@ -22,6 +22,7 @@ namespace Avalonia.Android
public InvalidationAwareSurfaceView(Context context) : base(context)
{
Holder.AddCallback(this);
Holder.SetFormat(global::Android.Graphics.Format.Transparent);
_handler = new Handler(context.MainLooper);
}

85
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -26,6 +26,7 @@ using Avalonia.Rendering.Composition;
using Java.Lang;
using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Android.Graphics.Drawables;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -283,7 +284,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public Action LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
@ -301,7 +302,87 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
throw new NotImplementedException();
if (TransparencyLevel != transparencyLevel)
{
bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R;
bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R;
if (_view.Context is AvaloniaMainActivity activity)
{
switch (transparencyLevel)
{
case WindowTransparencyLevel.AcrylicBlur:
case WindowTransparencyLevel.ForceAcrylicBlur:
case WindowTransparencyLevel.Mica:
case WindowTransparencyLevel.None:
if (!isBelowR)
{
activity.SetTranslucent(false);
}
if (isAboveR)
{
activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind);
var attr = activity.Window?.Attributes;
if (attr != null)
{
attr.BlurBehindRadius = 0;
activity.Window.Attributes = attr;
}
}
activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White));
if(transparencyLevel != WindowTransparencyLevel.None)
{
return;
}
break;
case WindowTransparencyLevel.Transparent:
if (!isBelowR)
{
activity.SetTranslucent(true);
}
if (isAboveR)
{
activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind);
var attr = activity.Window?.Attributes;
if (attr != null)
{
attr.BlurBehindRadius = 0;
activity.Window.Attributes = attr;
}
}
activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
break;
case WindowTransparencyLevel.Blur:
if (isAboveR)
{
activity.SetTranslucent(true);
activity.Window?.AddFlags(WindowManagerFlags.BlurBehind);
var attr = activity.Window?.Attributes;
if (attr != null)
{
attr.BlurBehindRadius = 120;
activity.Window.Attributes = attr;
}
activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
}
else
{
activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind);
activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White));
return;
}
break;
}
TransparencyLevel = transparencyLevel;
}
}
}
}

2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@ -38,7 +38,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return null;
}
var eventTime = (ulong)DateTime.Now.Millisecond;
var eventTime = (ulong)e.EventTime;
var inputRoot = _view.InputRoot;
var actionMasked = e.ActionMasked;
var modifiers = GetModifiers(e.MetaState, e.ButtonState);

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

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;

21
src/Avalonia.Base/Controls/NameScopeExtensions.cs

@ -25,13 +25,18 @@ namespace Avalonia.Controls
var result = nameScope.Find(name);
if (result != null && !(result is T))
if (result == null)
{
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
return null;
}
return (T?)result;
if (result is T typed)
{
return typed;
}
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
}
/// <summary>
@ -74,13 +79,13 @@ namespace Avalonia.Controls
throw new KeyNotFoundException($"Could not find control '{name}'.");
}
if (!(result is T))
if (result is T typed)
{
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
return typed;
}
return (T)result;
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
}
/// <summary>

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

43
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -17,7 +17,9 @@ namespace Avalonia.Input.GestureRecognizers
private bool _canVerticallyScroll;
private int _gestureId;
private int _scrollStartDistance = 30;
private Point _pointerPressedPoint;
private VelocityTracker? _velocityTracker;
// Movement per second
private Vector _inertia;
private ulong? _lastMoveTimestamp;
@ -91,7 +93,7 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
_tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId();
_trackedRootPoint = e.GetPosition((Visual?)_target);
_trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target);
}
}
@ -111,6 +113,13 @@ namespace Avalonia.Input.GestureRecognizers
_scrolling = true;
if (_scrolling)
{
_velocityTracker = new VelocityTracker();
// Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
_trackedRootPoint = new Point(
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance));
_actions!.Capture(e.Pointer, this);
}
}
@ -118,14 +127,11 @@ namespace Avalonia.Input.GestureRecognizers
if (_scrolling)
{
var vector = _trackedRootPoint - rootPoint;
var elapsed = _lastMoveTimestamp.HasValue && _lastMoveTimestamp < e.Timestamp ?
TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) :
TimeSpan.Zero;
_velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint);
_lastMoveTimestamp = e.Timestamp;
_trackedRootPoint = rootPoint;
if (elapsed.TotalSeconds > 0)
_inertia = vector / elapsed.TotalSeconds;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true;
}
@ -150,12 +156,14 @@ namespace Avalonia.Input.GestureRecognizers
}
}
public void PointerReleased(PointerReleasedEventArgs e)
{
if (e.Pointer == _tracking && _scrolling)
{
_inertia = _velocityTracker?.GetFlingVelocity().PixelsPerSecond ?? Vector.Zero;
e.Handled = true;
if (_inertia == default
|| e.Timestamp == 0
@ -183,9 +191,18 @@ namespace Avalonia.Input.GestureRecognizers
var distance = speed * elapsedSinceLastTick.TotalSeconds;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance));
if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
// EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
{
EndGesture();
return false;
}
else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
{
EndGesture();
return false;
}
else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd)
{
EndGesture();
return false;

375
src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs

@ -0,0 +1,375 @@
// Code in this file is derived from
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart
using System;
using System.Diagnostics;
using Avalonia.Utilities;
namespace Avalonia.Input.GestureRecognizers
{
// Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'?
internal readonly record struct Velocity(Vector PixelsPerSecond)
{
public Velocity ClampMagnitude(double minValue, double maxValue)
{
Debug.Assert(minValue >= 0.0);
Debug.Assert(maxValue >= 0.0 && maxValue >= minValue);
double valueSquared = PixelsPerSecond.SquaredLength;
if (valueSquared > maxValue * maxValue)
{
double length = PixelsPerSecond.Length;
return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * maxValue : Vector.Zero);
// preventing double.NaN in Vector PixelsPerSecond is important -- if a NaN eventually gets into a
// ScrollGestureEventArgs it results in runtime errors.
}
if (valueSquared < minValue * minValue)
{
double length = PixelsPerSecond.Length;
return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * minValue : Vector.Zero);
}
return this;
}
}
/// A two dimensional velocity estimate.
///
/// VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An
/// estimate's [confidence] measures how well the velocity tracker's position
/// data fit a straight line, [duration] is the time that elapsed between the
/// first and last position sample used to compute the velocity, and [offset]
/// is similarly the difference between the first and last positions.
///
/// See also:
///
/// * [VelocityTracker], which computes [VelocityEstimate]s.
/// * [Velocity], which encapsulates (just) a velocity vector and provides some
/// useful velocity operations.
internal record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset);
internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time);
/// Computes a pointer's velocity based on data from [PointerMoveEvent]s.
///
/// The input data is provided by calling [addPosition]. Adding data is cheap.
///
/// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will
/// compute the velocity based on the data added so far. Only call these when
/// you need to use the velocity, as they are comparatively expensive.
///
/// The quality of the velocity estimation will be better if more data points
/// have been received.
internal class VelocityTracker
{
private const int AssumePointerMoveStoppedMilliseconds = 40;
private const int HistorySize = 20;
private const int HorizonMilliseconds = 100;
private const int MinSampleSize = 3;
private const double MinFlingVelocity = 50.0; // Logical pixels / second
private const double MaxFlingVelocity = 8000.0;
private readonly PointAtTime[] _samples = new PointAtTime[HistorySize];
private int _index = 0;
/// <summary>
/// Adds a position as the given time to the tracker.
/// </summary>
/// <param name="time"></param>
/// <param name="position"></param>
public void AddPosition(TimeSpan time, Vector position)
{
_index++;
if (_index == HistorySize)
{
_index = 0;
}
_samples[_index] = new PointAtTime(true, position, time);
}
/// Returns an estimate of the velocity of the object being tracked by the
/// tracker given the current information available to the tracker.
///
/// Information is added using [addPosition].
///
/// Returns null if there is no data on which to base an estimate.
protected virtual VelocityEstimate? GetVelocityEstimate()
{
Span<double> x = stackalloc double[HistorySize];
Span<double> y = stackalloc double[HistorySize];
Span<double> w = stackalloc double[HistorySize];
Span<double> time = stackalloc double[HistorySize];
int sampleCount = 0;
int index = _index;
var newestSample = _samples[index];
if (!newestSample.Valid)
{
return null;
}
var previousSample = newestSample;
var oldestSample = newestSample;
// Starting with the most recent PointAtTime sample, iterate backwards while
// the samples represent continuous motion.
do
{
var sample = _samples[index];
if (!sample.Valid)
{
break;
}
double age = (newestSample.Time - sample.Time).TotalMilliseconds;
double delta = Math.Abs((sample.Time - previousSample.Time).TotalMilliseconds);
previousSample = sample;
if (age > HorizonMilliseconds || delta > AssumePointerMoveStoppedMilliseconds)
{
break;
}
oldestSample = sample;
var position = sample.Point;
x[sampleCount] = position.X;
y[sampleCount] = position.Y;
w[sampleCount] = 1.0;
time[sampleCount] = -age;
index = (index == 0 ? HistorySize : index) - 1;
sampleCount++;
} while (sampleCount < HistorySize);
if (sampleCount >= MinSampleSize)
{
var xFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), x.Slice(0, sampleCount), w.Slice(0, sampleCount));
if (xFit != null)
{
var yFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), y.Slice(0, sampleCount), w.Slice(0, sampleCount));
if (yFit != null)
{
return new VelocityEstimate( // convert from pixels/ms to pixels/s
PixelsPerSecond: new Vector(xFit.Coefficients[1] * 1000, yFit.Coefficients[1] * 1000),
Confidence: xFit.Confidence * yFit.Confidence,
Duration: newestSample.Time - oldestSample.Time,
Offset: newestSample.Point - oldestSample.Point
);
}
}
}
// We're unable to make a velocity estimate but we did have at least one
// valid pointer position.
return new VelocityEstimate(
PixelsPerSecond: Vector.Zero,
Confidence: 1.0,
Duration: newestSample.Time - oldestSample.Time,
Offset: newestSample.Point - oldestSample.Point
);
}
/// <summary>
/// Computes the velocity of the pointer at the time of the last
/// provided data point.
///
/// This can be expensive. Only call this when you need the velocity.
///
/// Returns [Velocity.zero] if there is no data from which to compute an
/// estimate or if the estimated velocity is zero.///
/// </summary>
/// <returns></returns>
internal Velocity GetVelocity()
{
var estimate = GetVelocityEstimate();
if (estimate == null || estimate.PixelsPerSecond.IsDefault)
{
return new Velocity(Vector.Zero);
}
return new Velocity(estimate.PixelsPerSecond);
}
internal virtual Velocity GetFlingVelocity()
{
return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity);
}
}
/// An nth degree polynomial fit to a dataset.
internal class PolynomialFit
{
/// Creates a polynomial fit of the given degree.
///
/// There are n + 1 coefficients in a fit of degree n.
internal PolynomialFit(int degree)
{
Coefficients = new double[degree + 1];
}
/// The polynomial coefficients of the fit.
public double[] Coefficients { get; }
/// An indicator of the quality of the fit.
///
/// Larger values indicate greater quality.
public double Confidence { get; set; }
}
internal class LeastSquaresSolver
{
private const double PrecisionErrorTolerance = 1e-10;
/// <summary>
/// Fits a polynomial of the given degree to the data points.
/// When there is not enough data to fit a curve null is returned.
/// </summary>
public static PolynomialFit? Solve(int degree, ReadOnlySpan<double> x, ReadOnlySpan<double> y, ReadOnlySpan<double> w)
{
if (degree > x.Length)
{
// Not enough data to fit a curve.
return null;
}
PolynomialFit result = new PolynomialFit(degree);
// Shorthands for the purpose of notation equivalence to original C++ code.
int m = x.Length;
int n = degree + 1;
// Expand the X vector to a matrix A, pre-multiplied by the weights.
_Matrix a = new _Matrix(m, stackalloc double[n * m]);
for (int h = 0; h < m; h += 1)
{
a[0, h] = w[h];
for (int i = 1; i < n; i += 1)
{
a[i, h] = a[i - 1, h] * x[h];
}
}
// Apply the Gram-Schmidt process to A to obtain its QR decomposition.
// Orthonormal basis, column-major order Vector.
_Matrix q = new _Matrix(m, stackalloc double[n * m]);
// Upper triangular matrix, row-major order.
_Matrix r = new _Matrix(n, stackalloc double[n * n]);
for (int j = 0; j < n; j += 1)
{
for (int h = 0; h < m; h += 1)
{
q[j, h] = a[j, h];
}
for (int i = 0; i < j; i += 1)
{
double dot = Multiply(q.GetRow(j), q.GetRow(i));
for (int h = 0; h < m; h += 1)
{
q[j, h] = q[j, h] - dot * q[i, h];
}
}
double norm = Norm(q.GetRow(j));
if (norm < PrecisionErrorTolerance)
{
// Vectors are linearly dependent or zero so no solution.
return null;
}
double inverseNorm = 1.0 / norm;
for (int h = 0; h < m; h += 1)
{
q[j, h] = q[j, h] * inverseNorm;
}
for (int i = 0; i < n; i += 1)
{
r[j, i] = i < j ? 0.0 : Multiply(q.GetRow(j), a.GetRow(i));
}
}
// Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
// We just work from bottom-right to top-left calculating B's coefficients.
// "m" isn't expected to be bigger than HistorySize=20, so allocation on stack is safe.
Span<double> wy = stackalloc double[m];
for (int h = 0; h < m; h += 1)
{
wy[h] = y[h] * w[h];
}
for (int i = n - 1; i >= 0; i -= 1)
{
result.Coefficients[i] = Multiply(q.GetRow(i), wy);
for (int j = n - 1; j > i; j -= 1)
{
result.Coefficients[i] -= r[i, j] * result.Coefficients[j];
}
result.Coefficients[i] /= r[i, i];
}
// Calculate the coefficient of determination (confidence) as:
// 1 - (sumSquaredError / sumSquaredTotal)
// ...where sumSquaredError is the residual sum of squares (variance of the
// error), and sumSquaredTotal is the total sum of squares (variance of the
// data) where each has been weighted.
double yMean = 0.0;
for (int h = 0; h < m; h += 1)
{
yMean += y[h];
}
yMean /= m;
double sumSquaredError = 0.0;
double sumSquaredTotal = 0.0;
for (int h = 0; h < m; h += 1)
{
double term = 1.0;
double err = y[h] - result.Coefficients[0];
for (int i = 1; i < n; i += 1)
{
term *= x[h];
err -= term * result.Coefficients[i];
}
sumSquaredError += w[h] * w[h] * err * err;
double v = y[h] - yMean;
sumSquaredTotal += w[h] * w[h] * v * v;
}
result.Confidence = sumSquaredTotal <= PrecisionErrorTolerance ? 1.0 :
1.0 - (sumSquaredError / sumSquaredTotal);
return result;
}
private static double Multiply(Span<double> v1, Span<double> v2)
{
double result = 0.0;
for (int i = 0; i < v1.Length; i += 1)
{
result += v1[i] * v2[i];
}
return result;
}
private static double Norm(Span<double> v)
{
return Math.Sqrt(Multiply(v, v));
}
private readonly ref struct _Matrix
{
private readonly int _columns;
private readonly Span<double> _elements;
internal _Matrix(int cols, Span<double> elements)
{
_columns = cols;
_elements = elements;
}
public double this[int row, int col]
{
get => _elements[row * _columns + col];
set => _elements[row * _columns + col] = value;
}
public Span<double> GetRow(int row) => _elements.Slice(row * _columns, _columns);
}
}
}

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;

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

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

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
{

6
src/Avalonia.Base/Interactivity/RoutedEvent.cs

@ -1,5 +1,5 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Reactive;
namespace Avalonia.Interactivity
{
@ -13,8 +13,8 @@ namespace Avalonia.Interactivity
public class RoutedEvent
{
private readonly Subject<(object, RoutedEventArgs)> _raised = new Subject<(object, RoutedEventArgs)>();
private readonly Subject<RoutedEventArgs> _routeFinished = new Subject<RoutedEventArgs>();
private readonly LightweightSubject<(object, RoutedEventArgs)> _raised = new();
private readonly LightweightSubject<RoutedEventArgs> _routeFinished = new();
public RoutedEvent(
string name,

18
src/Avalonia.Base/Layout/Layoutable.cs

@ -1,6 +1,6 @@
using System;
using Avalonia.Logging;
using Avalonia.Styling;
using Avalonia.Reactive;
using Avalonia.VisualTree;
#nullable enable
@ -470,14 +470,12 @@ namespace Avalonia.Layout
protected static void AffectsMeasure<T>(params AvaloniaProperty[] properties)
where T : Layoutable
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.InvalidateMeasure();
}
var invalidateObserver = new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(
static e => (e.Sender as T)?.InvalidateMeasure());
foreach (var property in properties)
{
property.Changed.Subscribe(Invalidate);
property.Changed.Subscribe(invalidateObserver);
}
}
@ -493,14 +491,12 @@ namespace Avalonia.Layout
protected static void AffectsArrange<T>(params AvaloniaProperty[] properties)
where T : Layoutable
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.InvalidateArrange();
}
var invalidate = new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(
static e => (e.Sender as T)?.InvalidateArrange());
foreach (var property in properties)
{
property.Changed.Subscribe(Invalidate);
property.Changed.Subscribe(invalidate);
}
}

2
src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs

@ -7,7 +7,7 @@ namespace Avalonia.Layout
{
internal struct UvMeasure
{
internal static readonly UvMeasure Zero = default(UvMeasure);
internal static readonly UvMeasure Zero = default;
internal double U { get; set; }

9
src/Avalonia.Base/Media/Brush.cs

@ -3,6 +3,7 @@ using System.ComponentModel;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;
using Avalonia.Reactive;
namespace Avalonia.Media
{
@ -103,14 +104,12 @@ namespace Avalonia.Media
protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
where T : Brush
{
static void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.RaiseInvalidated(EventArgs.Empty);
}
var invalidateObserver = new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(
static e => (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty));
foreach (var property in properties)
{
property.Changed.Subscribe(e => Invalidate(e));
property.Changed.Subscribe(invalidateObserver);
}
}

11
src/Avalonia.Base/Media/DashStyle.cs

@ -4,6 +4,7 @@ using System.Collections.Specialized;
using Avalonia.Animation;
using Avalonia.Collections;
using Avalonia.Media.Immutable;
using Avalonia.Reactive;
#nullable enable
@ -51,13 +52,11 @@ namespace Avalonia.Media
static DashStyle()
{
void RaiseInvalidated(AvaloniaPropertyChangedEventArgs e)
{
((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty);
}
var invalidateObserver = new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(
static e => ((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty));
DashesProperty.Changed.Subscribe(RaiseInvalidated);
OffsetProperty.Changed.Subscribe(RaiseInvalidated);
DashesProperty.Changed.Subscribe(invalidateObserver);
OffsetProperty.Changed.Subscribe(invalidateObserver);
}
/// <summary>

2
src/Avalonia.Base/Media/DrawingContext.cs

@ -279,7 +279,7 @@ namespace Avalonia.Media
OpacityMask,
}
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix))
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default)
{
if (context._states is null)
throw new ObjectDisposedException(nameof(DrawingContext));

4
src/Avalonia.Base/Media/DrawingGroup.cs

@ -76,8 +76,8 @@ namespace Avalonia.Media
{
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushOpacity(Opacity))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default(DrawingContext.PushedState))
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default(DrawingContext.PushedState))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default)
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default)
{
foreach (var drawing in Children)
{

9
src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Reactive;
namespace Avalonia.Media
{
@ -274,14 +275,12 @@ namespace Avalonia.Media
protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
where T : ExperimentalAcrylicMaterial
{
static void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.RaiseInvalidated(EventArgs.Empty);
}
var invalidateObserver = new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(
static e => (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty));
foreach (var property in properties)
{
property.Changed.Subscribe(e => Invalidate(e));
property.Changed.Subscribe(invalidateObserver);
}
}

5
src/Avalonia.Base/Media/FontManager.cs

@ -47,10 +47,7 @@ namespace Avalonia.Media
return current;
}
var fontManagerImpl = AvaloniaLocator.Current.GetService<IFontManagerImpl>();
if (fontManagerImpl == null)
throw new InvalidOperationException("No font manager implementation was registered.");
var fontManagerImpl = AvaloniaLocator.Current.GetRequiredService<IFontManagerImpl>();
current = new FontManager(fontManagerImpl);

5
src/Avalonia.Base/Media/Geometry.cs

@ -2,6 +2,8 @@ using System;
using Avalonia.Platform;
using System.ComponentModel;
using System.Globalization;
using Avalonia.Reactive;
namespace Avalonia.Media
{
@ -120,9 +122,10 @@ namespace Avalonia.Media
/// </remarks>
protected static void AffectsGeometry(params AvaloniaProperty[] properties)
{
var invalidateObserver = new AnonymousObserver<AvaloniaPropertyChangedEventArgs>(AffectsGeometryInvalidate);
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsGeometryInvalidate);
property.Changed.Subscribe(invalidateObserver);
}
}

1
src/Avalonia.Base/Media/GradientBrush.cs

@ -6,6 +6,7 @@ using System.ComponentModel;
using Avalonia.Animation.Animators;
using Avalonia.Collections;
using Avalonia.Metadata;
using Avalonia.Reactive;
namespace Avalonia.Media
{

2
src/Avalonia.Base/Media/ImmediateDrawingContext.cs

@ -218,7 +218,7 @@ namespace Avalonia.Media
OpacityMask,
}
internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default(Matrix))
internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default)
{
if (context._states is null)
throw new ObjectDisposedException(nameof(ImmediateDrawingContext));

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

Loading…
Cancel
Save