Browse Source

Merge branch 'master' into disabled-controls-should-not-get-keyboard-events

pull/9627/head
Max Katz 3 years ago
committed by GitHub
parent
commit
0307de4500
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .editorconfig
  2. 5
      .ncrunch/ReactiveUIDemo.v3.ncrunchproject
  3. 1
      Avalonia.Desktop.slnf
  4. 13
      Avalonia.sln
  5. 2
      build/SharedVersion.props
  6. 16
      build/TrimmingEnable.props
  7. 9
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  8. 1
      nukebuild/Build.cs
  9. 2
      nukebuild/_build.csproj
  10. 3
      samples/ControlCatalog/MainView.xaml
  11. 27
      samples/ControlCatalog/Pages/RefreshContainerPage.axaml
  12. 36
      samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs
  13. 26
      samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs
  14. 15
      samples/IntegrationTestApp/MainWindow.axaml
  15. 3
      samples/IntegrationTestApp/MainWindow.axaml.cs
  16. 8
      samples/IntegrationTestApp/ShowWindowTest.axaml
  17. 8
      samples/ReactiveUIDemo/App.axaml
  18. 37
      samples/ReactiveUIDemo/App.axaml.cs
  19. 19
      samples/ReactiveUIDemo/MainWindow.axaml
  20. 22
      samples/ReactiveUIDemo/MainWindow.axaml.cs
  21. 28
      samples/ReactiveUIDemo/ReactiveUIDemo.csproj
  22. 11
      samples/ReactiveUIDemo/ViewModels/BarViewModel.cs
  23. 11
      samples/ReactiveUIDemo/ViewModels/FooViewModel.cs
  24. 9
      samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs
  25. 21
      samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs
  26. 16
      samples/ReactiveUIDemo/Views/BarView.axaml
  27. 28
      samples/ReactiveUIDemo/Views/BarView.axaml.cs
  28. 16
      samples/ReactiveUIDemo/Views/FooView.axaml
  29. 28
      samples/ReactiveUIDemo/Views/FooView.axaml.cs
  30. 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  31. 2
      src/Avalonia.Base/Animation/Animation.cs
  32. 4
      src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
  33. 3
      src/Avalonia.Base/Animation/Animators/Animator`1.cs
  34. 10
      src/Avalonia.Base/Avalonia.Base.csproj
  35. 2
      src/Avalonia.Base/AvaloniaProperty.cs
  36. 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  37. 2
      src/Avalonia.Base/AvaloniaProperty`1.cs
  38. 4
      src/Avalonia.Base/Collections/AvaloniaListConverter.cs
  39. 121
      src/Avalonia.Base/Compatibility/TrimmingAttributes.cs
  40. 3
      src/Avalonia.Base/Data/BindingValue.cs
  41. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  42. 2
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  43. 2
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  44. 5
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  45. 4
      src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs
  46. 2
      src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
  47. 3
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  48. 4
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  49. 3
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  50. 3
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  51. 3
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  52. 3
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  53. 3
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  54. 10
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  55. 9
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  56. 5
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  57. 7
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  58. 3
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  59. 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  60. 30
      src/Avalonia.Base/Diagnostics/TrimmingMessages.cs
  61. 152
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  62. 8
      src/Avalonia.Base/Input/Gestures.cs
  63. 43
      src/Avalonia.Base/Input/PullGestureEventArgs.cs
  64. 2
      src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs
  65. 2
      src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs
  66. 67
      src/Avalonia.Base/StyledElement.cs
  67. 2
      src/Avalonia.Base/StyledPropertyBase.cs
  68. 5
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  69. 2
      src/Avalonia.Base/Styling/Setter.cs
  70. 3
      src/Avalonia.Base/Styling/StyleInstance.cs
  71. 3
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  72. 13
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  73. 8
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  74. 90
      src/Avalonia.Base/Visual.cs
  75. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  76. 1
      src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj
  77. 4
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  78. 50
      src/Avalonia.Controls/AppBuilderBase.cs
  79. 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  80. 5
      src/Avalonia.Controls/ComboBox.cs
  81. 85
      src/Avalonia.Controls/Control.cs
  82. 12
      src/Avalonia.Controls/NativeMenuBar.cs
  83. 2
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  84. 36
      src/Avalonia.Controls/PullToRefresh/RefreshCompletionDeferral.cs
  85. 252
      src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs
  86. 141
      src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs
  87. 42
      src/Avalonia.Controls/PullToRefresh/RefreshRequestedEventArgs.cs
  88. 553
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  89. 13
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizerOrientation.cs
  90. 14
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizerState.cs
  91. 274
      src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
  92. 6
      src/Avalonia.Controls/TopLevel.cs
  93. 2
      src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj
  94. 13
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  95. 4
      src/Avalonia.Desktop/Avalonia.Desktop.csproj
  96. 1
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  97. 1
      src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
  98. 6
      src/Avalonia.Headless/Avalonia.Headless.csproj
  99. 2
      src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
  100. 2
      src/Avalonia.MicroCom/CallbackBase.cs

5
.editorconfig

@ -64,7 +64,7 @@ dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case
dotnet_naming_style.static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true
#Net Analyzer
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = suggestion
# CA1304: Specify CultureInfo
dotnet_diagnostic.CA1304.severity = warning
# CA1802: Use literals where appropriate

5
.ncrunch/ReactiveUIDemo.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

1
Avalonia.Desktop.slnf

@ -9,6 +9,7 @@
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",

13
Avalonia.sln

@ -40,6 +40,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE
.editorconfig = .editorconfig
src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs
src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs
src\Shared\NullableAttributes.cs = src\Shared\NullableAttributes.cs
src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs
src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs = src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs
EndProjectSection
@ -119,6 +120,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\SourceLink.props = build\SourceLink.props
build\System.Drawing.Common.props = build\System.Drawing.Common.props
build\System.Memory.props = build\System.Memory.props
build\TrimmingEnable.props = build\TrimmingEnable.props
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props
EndProjectSection
@ -222,14 +224,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "sample
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser", "src\Browser\Avalonia.Browser\Avalonia.Browser.csproj", "{4A39637C-9338-4925-A4DB-D072E292EC78}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser", "src\Browser\Avalonia.Browser\Avalonia.Browser.csproj", "{4A39637C-9338-4925-A4DB-D072E292EC78}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -541,6 +545,10 @@ Global
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.Build.0 = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -605,6 +613,7 @@ Global
{47F8530C-F19B-4B1A-B4D6-EB231522AE5D} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

2
build/SharedVersion.props

@ -7,7 +7,7 @@
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591</NoWarn>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<LangVersion>preview</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>

16
build/TrimmingEnable.props

@ -0,0 +1,16 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<!-- Remove check for the AOT when we get rid of dependencies with reflection -->
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0' and '$(PublishAot)' != 'true'">
<ILLinkTreatWarningsAsErrors>true</ILLinkTreatWarningsAsErrors>
<!-- Trim warnings -->
<WarningsAsErrors>$(WarningsAsErrors);IL2000;IL2001;IL2002;IL2003;IL2004;IL2005;IL2006;IL2007;IL2008;IL2009;IL2010;IL2011;IL2012;IL2013;IL2014;IL2015;IL2016;IL2017;IL2018;IL2019;IL2020;IL2021;IL2022;IL2023;IL2024;IL2025;IL2026;IL2027;IL2028;IL2029;IL2030;IL2031;IL2032;IL2033;IL2034;IL2035;IL2036;IL2037;IL2038;IL2039;IL2040;IL2041;IL2042;IL2043;IL2044;IL2045;IL2046;IL2047;IL2048;IL2049;IL2050;IL2051;IL2052;IL2053;IL2054;IL2055;IL2056;IL2057;IL2058;IL2059;IL2060;IL2061;IL2062;IL2063;IL2064;IL2065;IL2066;IL2067;IL2068;IL2069;IL2070;IL2071;IL2072;IL2073;IL2074;IL2075;IL2076;IL2077;IL2078;IL2079;IL2080;IL2081;IL2082;IL2083;IL2084;IL2085;IL2086;IL2087;IL2088;IL2089;IL2090;IL2091;IL2092;IL2093;IL2094;IL2095;IL2096;IL2097;IL2098;IL2099;IL2100;IL2101;IL2102;IL2103;IL2104;IL2105;IL2106;IL2107;IL2108;IL2109;IL2110;IL2111;IL2112;IL2113;IL2114;IL2115;IL2116;IL2117;IL2118;IL2119;IL2120;IL2121;IL2122;IL2123;IL2124;IL2125;IL2126;IL2127;IL2128;IL2129;IL2130;IL2131;IL2132;IL2133;IL2134;IL2135;IL2136;IL2137;IL2138;IL2139;IL2140;IL2141;IL2142;IL2143;IL2144;IL2145;IL2146;IL2147;IL2148;IL2149;IL2150;IL2151;IL2152;IL2153;IL2154;IL2155;IL2156;IL2157</WarningsAsErrors>
<!-- NativeAOT warnings -->
<WarningsAsErrors>$(WarningsAsErrors);IL3050;IL3051;IL3052;IL3053;IL3054;IL3055;IL3056</WarningsAsErrors>
</PropertyGroup>
</Project>

9
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -137,7 +137,11 @@ void WindowImpl::BringToFront()
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
(*iterator)->BringToFront();
auto window = (*iterator)->Window;
// #9565: Only bring window to front if it's on the currently active space
if ([window isOnActiveSpace])
(*iterator)->BringToFront();
}
}
}
@ -161,6 +165,9 @@ void WindowImpl::StartStateTransition() {
void WindowImpl::EndStateTransition() {
_transitioningWindowState = false;
// Ensure correct order of child windows after fullscreen transition.
BringToFront();
}
SystemDecorations WindowImpl::Decorations() {

1
nukebuild/Build.cs

@ -80,7 +80,6 @@ partial class Build : NukeBuild
if (Parameters.IsRunningOnAzure)
c.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64"));
c.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", true)
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(DotNetVerbosity.Minimal);
return c;

2
nukebuild/_build.csproj

@ -4,7 +4,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace></RootNamespace>
<IsPackable>False</IsPackable>
<NoWarn>CS0649;CS0169;SYSLIB0011</NoWarn>
<NoWarn>$(NoWarn);CS0649;CS0169;SYSLIB0011</NoWarn>
<NukeTelemetryVersion>1</NukeTelemetryVersion>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>

3
samples/ControlCatalog/MainView.xaml

@ -135,6 +135,9 @@
<TabItem Header="RadioButton">
<pages:RadioButtonPage />
</TabItem>
<TabItem Header="RefreshContainer">
<pages:RefreshContainerPage />
</TabItem>
<TabItem Header="RelativePanel">
<pages:RelativePanelPage />
</TabItem>

27
samples/ControlCatalog/Pages/RefreshContainerPage.axaml

@ -0,0 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:ControlCatalog.ViewModels"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:DataType="viewModels:RefreshContainerViewModel"
x:Class="ControlCatalog.Pages.RefreshContainerPage">
<DockPanel HorizontalAlignment="Stretch"
Height="600"
VerticalAlignment="Top">
<Label DockPanel.Dock="Top">A control that supports pull to refresh</Label>
<RefreshContainer Name="Refresh"
DockPanel.Dock="Bottom"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
PullDirection="TopToBottom"
RefreshRequested="RefreshContainerPage_RefreshRequested"
Margin="5">
<ListBox HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Items="{Binding Items}"/>
</RefreshContainer>
</DockPanel>
</UserControl>

36
samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs

@ -0,0 +1,36 @@
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
public class RefreshContainerPage : UserControl
{
private RefreshContainerViewModel _viewModel;
public RefreshContainerPage()
{
this.InitializeComponent();
_viewModel = new RefreshContainerViewModel();
DataContext = _viewModel;
}
private async void RefreshContainerPage_RefreshRequested(object? sender, RefreshRequestedEventArgs e)
{
var deferral = e.GetDeferral();
await _viewModel.AddToTop();
deferral.Complete();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

26
samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs

@ -0,0 +1,26 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Controls.Notifications;
using ControlCatalog.Pages;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class RefreshContainerViewModel : ViewModelBase
{
public ObservableCollection<string> Items { get; }
public RefreshContainerViewModel()
{
Items = new ObservableCollection<string>(Enumerable.Range(1, 200).Select(i => $"Item {i}"));
}
public async Task AddToTop()
{
await Task.Delay(3000);
Items.Insert(0, $"Item {200 - Items.Count}");
}
}
}

15
samples/IntegrationTestApp/MainWindow.axaml

@ -17,11 +17,15 @@
</NativeMenuItem>
<NativeMenuItem Header="View">
<NativeMenu/>
</NativeMenuItem>
</NativeMenuItem>
</NativeMenu>
</NativeMenu.Menu>
<DockPanel>
<NativeMenuBar DockPanel.Dock="Top"/>
<StackPanel DockPanel.Dock="Bottom" Margin="4" Orientation="Horizontal">
<TextBlock Margin="0,0,4,0">WindowState:</TextBlock>
<TextBlock Name="MainWindowState" Text="{Binding WindowState}"/>
</StackPanel>
<TabControl TabStripPlacement="Left" Name="MainTabs">
<TabItem Header="Automation">
@ -129,13 +133,14 @@
<ComboBoxItem>CenterOwner</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowState" SelectedIndex="0">
<ComboBoxItem>Normal</ComboBoxItem>
<ComboBoxItem>Minimized</ComboBoxItem>
<ComboBoxItem>Maximized</ComboBoxItem>
<ComboBoxItem>FullScreen</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateNormal">Normal</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateMinimized">Minimized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>
<Button Name="EnterFullscreen">Enter Fullscreen</Button>
<Button Name="ExitFullscreen">Exit Fullscreen</Button>
<Button Name="RestoreAll">Restore All</Button>
</StackPanel>

3
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
@ -178,6 +179,8 @@ namespace IntegrationTestApp
ShowWindow();
if (source?.Name == "SendToBack")
SendToBack();
if (source?.Name == "EnterFullscreen")
WindowState = WindowState.FullScreen;
if (source?.Name == "ExitFullscreen")
WindowState = WindowState.Normal;
if (source?.Name == "RestoreAll")

8
samples/IntegrationTestApp/ShowWindowTest.axaml

@ -27,10 +27,10 @@
<Label Grid.Column="0" Grid.Row="7">WindowState</Label>
<ComboBox Name="WindowState" Grid.Column="1" Grid.Row="7" SelectedIndex="{Binding WindowState}">
<ComboBoxItem>Normal</ComboBoxItem>
<ComboBoxItem>Minimized</ComboBoxItem>
<ComboBoxItem>Maximized</ComboBoxItem>
<ComboBoxItem>FullScreen</ComboBoxItem>
<ComboBoxItem Name="WindowStateNormal">Normal</ComboBoxItem>
<ComboBoxItem Name="WindowStateMinimized">Minimized</ComboBoxItem>
<ComboBoxItem Name="WindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>

8
samples/ReactiveUIDemo/App.axaml

@ -0,0 +1,8 @@
<Application
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ReactiveUIDemo.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

37
samples/ReactiveUIDemo/App.axaml.cs

@ -0,0 +1,37 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
using ReactiveUIDemo.ViewModels;
using ReactiveUIDemo.Views;
using Splat;
namespace ReactiveUIDemo
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
Locator.CurrentMutable.Register(() => new FooView(), typeof(IViewFor<FooViewModel>));
Locator.CurrentMutable.Register(() => new BarView(), typeof(IViewFor<BarViewModel>));
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow();
base.OnFrameworkInitializationCompleted();
}
public static int Main(string[] args)
=> BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.LogToTrace();
}
}

19
samples/ReactiveUIDemo/MainWindow.axaml

@ -0,0 +1,19 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="ReactiveUIDemo.MainWindow"
xmlns:vm="using:ReactiveUIDemo.ViewModels"
xmlns:rxui="using:Avalonia.ReactiveUI"
Title="AvaloniaUI ReactiveUI Demo"
x:DataType="vm:MainWindowViewModel">
<TabControl TabStripPlacement="Left">
<TabItem Header="RoutedViewHost">
<DockPanel DataContext="{Binding RoutedViewHost}">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8">
<Button Command="{Binding ShowFoo}">Foo</Button>
<Button Command="{Binding ShowBar}">Bar</Button>
</StackPanel>
<rxui:RoutedViewHost Router="{Binding Router}"/>
</DockPanel>
</TabItem>
</TabControl>
</Window>

22
samples/ReactiveUIDemo/MainWindow.axaml.cs

@ -0,0 +1,22 @@
using ReactiveUIDemo.ViewModels;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReactiveUIDemo
{
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.DataContext = new MainWindowViewModel();
this.AttachDevTools();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

28
samples/ReactiveUIDemo/ReactiveUIDemo.csproj

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\BarView.axaml.cs">
<DependentUpon>BarView.axaml</DependentUpon>
</Compile>
<Compile Update="Views\FooView.axaml.cs">
<DependentUpon>FooView.axaml</DependentUpon>
</Compile>
</ItemGroup>
<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>

11
samples/ReactiveUIDemo/ViewModels/BarViewModel.cs

@ -0,0 +1,11 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class BarViewModel : ReactiveObject, IRoutableViewModel
{
public BarViewModel(IScreen screen) => HostScreen = screen;
public string UrlPathSegment => "Bar";
public IScreen HostScreen { get; }
}
}

11
samples/ReactiveUIDemo/ViewModels/FooViewModel.cs

@ -0,0 +1,11 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class FooViewModel : ReactiveObject, IRoutableViewModel
{
public FooViewModel(IScreen screen) => HostScreen = screen;
public string UrlPathSegment => "Foo";
public IScreen HostScreen { get; }
}
}

9
samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs

@ -0,0 +1,9 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class MainWindowViewModel : ReactiveObject
{
public RoutedViewHostPageViewModel RoutedViewHost { get; } = new();
}
}

21
samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs

@ -0,0 +1,21 @@
using ReactiveUI;
namespace ReactiveUIDemo.ViewModels
{
internal class RoutedViewHostPageViewModel : ReactiveObject, IScreen
{
public RoutedViewHostPageViewModel()
{
Foo = new(this);
Bar = new(this);
Router.Navigate.Execute(Foo);
}
public RoutingState Router { get; } = new();
public FooViewModel Foo { get; }
public BarViewModel Bar { get; }
public void ShowFoo() => Router.Navigate.Execute(Foo);
public void ShowBar() => Router.Navigate.Execute(Bar);
}
}

16
samples/ReactiveUIDemo/Views/BarView.axaml

@ -0,0 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReactiveUIDemo.Views.BarView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border Background="Blue">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48">
Bar!
</TextBlock>
</Border>
</UserControl>

28
samples/ReactiveUIDemo/Views/BarView.axaml.cs

@ -0,0 +1,28 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using ReactiveUIDemo.ViewModels;
namespace ReactiveUIDemo.Views
{
internal partial class BarView : UserControl, IViewFor<BarViewModel>
{
public BarView()
{
InitializeComponent();
}
public BarViewModel? ViewModel { get; set; }
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (BarViewModel?)value;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

16
samples/ReactiveUIDemo/Views/FooView.axaml

@ -0,0 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReactiveUIDemo.Views.FooView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border Background="Red">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48">
Foo!
</TextBlock>
</Border>
</UserControl>

28
samples/ReactiveUIDemo/Views/FooView.axaml.cs

@ -0,0 +1,28 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using ReactiveUIDemo.ViewModels;
namespace ReactiveUIDemo.Views
{
internal partial class FooView : UserControl, IViewFor<FooViewModel>
{
public FooView()
{
InitializeComponent();
}
public FooViewModel? ViewModel { get; set; }
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (FooViewModel?)value;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

1
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -16,4 +16,5 @@
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />
</Project>

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

@ -202,9 +202,7 @@ namespace Avalonia.Animation
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter,
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)]
#endif
Type value)
{
s_animators[setter] = (value, () => (IAnimator)Activator.CreateInstance(value)!);

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

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Animation.Animators;
using Avalonia.Data;
using Avalonia.Reactive;
@ -66,7 +67,8 @@ namespace Avalonia.Animation
}
}
public T GetTypedValue<T>()
[RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
public T GetTypedValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
{
var typeConv = TypeDescriptor.GetConverter(typeof(T));

3
src/Avalonia.Base/Animation/Animators/Animator`1.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
@ -39,7 +40,7 @@ namespace Avalonia.Animation.Animators
VerifyConvertKeyFrames();
var subject = new DisposeAnimationInstanceSubject<T>(this, animation, control, clock, onComplete);
return match.Subscribe(subject);
return new CompositeDisposable(match.Subscribe(subject), subject);
}
protected T InterpolationHandler(double animationTime, T neutralValue)

10
src/Avalonia.Base/Avalonia.Base.csproj

@ -19,6 +19,7 @@
<Import Project="..\..\build\System.Memory.props" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" />
<ItemGroup>
@ -30,6 +31,8 @@
<InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.Xaml, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.ColorPicker, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.DataGrid, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
@ -41,6 +44,7 @@
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
@ -48,14 +52,10 @@
</ItemGroup>
<ItemGroup Label="Build dependency">
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj"
SetTargetFramework="TargetFramework=netstandard2.0"
ReferenceOutputAssembly="false"
SkipGetTargetFrameworkProperties="true" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj" SetTargetFramework="TargetFramework=netstandard2.0" ReferenceOutputAssembly="false" SkipGetTargetFrameworkProperties="true" />
</ItemGroup>
<ItemGroup>
<Folder Include="Compatibility\" />
<Folder Include="Rendering\Composition\Utils" />
</ItemGroup>
</Project>

2
src/Avalonia.Base/AvaloniaProperty.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.PropertyStore;
@ -442,6 +443,7 @@ namespace Avalonia
/// </summary>
/// <param name="value">The value.</param>
/// <returns>True if the value is valid, otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public bool IsValidValue(object? value)
{
return TypeUtilities.TryConvertImplicit(PropertyType, value, out _);

2
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Avalonia
@ -42,6 +43,7 @@ namespace Avalonia
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
[UnconditionalSuppressMessage("Trimming", "IL2059", Justification = "If type was trimmed out, no properties were referenced")]
public IReadOnlyList<AvaloniaProperty> GetRegistered(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));

2
src/Avalonia.Base/AvaloniaProperty`1.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Utilities;
@ -67,6 +68,7 @@ namespace Avalonia
protected override IObservable<AvaloniaPropertyChangedEventArgs> GetChanged() => Changed;
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
protected BindingValue<object?> TryConvert(object? value)
{
if (value == UnsetValue)

4
src/Avalonia.Base/Collections/AvaloniaListConverter.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Utilities;
@ -8,7 +9,8 @@ namespace Avalonia.Collections
/// <summary>
/// Creates an <see cref="AvaloniaList{T}"/> from a string representation.
/// </summary>
public class AvaloniaListConverter<T> : TypeConverter
[RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
public class AvaloniaListConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{

121
src/Avalonia.Base/Compatibility/TrimmingAttributes.cs

@ -0,0 +1,121 @@
#pragma warning disable MA0048 // File name must match type name
// https://github.com/dotnet/runtime/tree/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace System.Diagnostics.CodeAnalysis
{
#nullable enable
#if !NET6_0_OR_GREATER
[AttributeUsage(
AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter |
AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method |
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct,
Inherited = false)]
internal sealed class DynamicallyAccessedMembersAttribute : Attribute
{
public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes)
{
MemberTypes = memberTypes;
}
public DynamicallyAccessedMemberTypes MemberTypes { get; }
}
[Flags]
internal enum DynamicallyAccessedMemberTypes
{
None = 0,
PublicParameterlessConstructor = 0x0001,
PublicConstructors = 0x0002 | PublicParameterlessConstructor,
NonPublicConstructors = 0x0004,
PublicMethods = 0x0008,
NonPublicMethods = 0x0010,
PublicFields = 0x0020,
NonPublicFields = 0x0040,
PublicNestedTypes = 0x0080,
NonPublicNestedTypes = 0x0100,
PublicProperties = 0x0200,
NonPublicProperties = 0x0400,
PublicEvents = 0x0800,
NonPublicEvents = 0x1000,
Interfaces = 0x2000,
All = ~None
}
[AttributeUsage(
AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method,
AllowMultiple = true, Inherited = false)]
internal sealed class DynamicDependencyAttribute : Attribute
{
public DynamicDependencyAttribute(string memberSignature)
{
MemberSignature = memberSignature;
}
public DynamicDependencyAttribute(string memberSignature, Type type)
{
MemberSignature = memberSignature;
Type = type;
}
public DynamicDependencyAttribute(string memberSignature, string typeName, string assemblyName)
{
MemberSignature = memberSignature;
TypeName = typeName;
AssemblyName = assemblyName;
}
public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type)
{
MemberTypes = memberTypes;
Type = type;
}
public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, string typeName, string assemblyName)
{
MemberTypes = memberTypes;
TypeName = typeName;
AssemblyName = assemblyName;
}
public string? MemberSignature { get; }
public DynamicallyAccessedMemberTypes MemberTypes { get; }
public Type? Type { get; }
public string? TypeName { get; }
public string? AssemblyName { get; }
public string? Condition { get; set; }
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
internal sealed class RequiresUnreferencedCodeAttribute : Attribute
{
public RequiresUnreferencedCodeAttribute(string message)
{
Message = message;
}
public string Message { get; }
public string? Url { get; set; }
}
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
internal sealed class UnconditionalSuppressMessageAttribute : Attribute
{
public UnconditionalSuppressMessageAttribute(string category, string checkId)
{
Category = category;
CheckId = checkId;
}
public string Category { get; }
public string CheckId { get; }
public string? Scope { get; set; }
public string? Target { get; set; }
public string? MessageId { get; set; }
public string? Justification { get; set; }
}
#endif
}

3
src/Avalonia.Base/Data/BindingValue.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Utilities;
namespace Avalonia.Data
@ -236,6 +237,7 @@ namespace Avalonia.Data
/// </summary>
/// <param name="value">The untyped value.</param>
/// <returns>The typed binding value.</returns>
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public static BindingValue<T> FromUntyped(object? value)
{
return FromUntyped(value, typeof(T));
@ -249,6 +251,7 @@ namespace Avalonia.Data
/// <param name="value">The untyped value.</param>
/// <param name="targetType">The runtime target type.</param>
/// <returns>The typed binding value.</returns>
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public static BindingValue<T> FromUntyped(object? value, Type targetType)
{
if (value == AvaloniaProperty.UnsetValue)

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Windows.Input;
using Avalonia.Utilities;
@ -9,6 +10,7 @@ namespace Avalonia.Data.Converters
/// Provides a default set of value conversions for bindings that do not specify a value
/// converter.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
public class DefaultValueConverter : IValueConverter
{
/// <summary>

2
src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
@ -9,6 +10,7 @@ using Avalonia.Utilities;
namespace Avalonia.Data.Converters
{
[RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)]
class MethodToCommandConverter : ICommand
{
readonly static Func<object?, bool> AlwaysEnabled = (_) => true;

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

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reactive.Linq;
using System.Reactive.Subjects;
@ -13,6 +14,7 @@ namespace Avalonia.Data.Core
/// Binds to an expression on an object using a type value converter to convert the values
/// that are sent and received.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
public class BindingExpression : LightweightObservableBase<object?>, ISubject<object?>, IDescription
{
private readonly ExpressionObserver _inner;

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

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
@ -126,6 +127,7 @@ namespace Avalonia.Data.Core
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)]
public static ExpressionObserver Create<T, U>(
T? root,
Expression<Func<T, U>> expression,
@ -144,6 +146,7 @@ namespace Avalonia.Data.Core
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)]
public static ExpressionObserver Create<T, U>(
IObservable<T> rootObservable,
Expression<Func<T, U>> expression,
@ -168,6 +171,7 @@ namespace Avalonia.Data.Core
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)]
public static ExpressionObserver Create<T, U>(
Func<T> rootGetter,
Expression<Func<T, U>> expression,
@ -283,6 +287,7 @@ namespace Avalonia.Data.Core
}
}
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation)
{
return ExpressionTreeParser.Parse(expression, enableDataValidation);

4
src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs

@ -1,10 +1,12 @@
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
namespace Avalonia.Data.Core.Parsers
{
static class ExpressionTreeParser
{
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
public static ExpressionNode Parse(Expression expr, bool enableDataValidation)
{
var visitor = new ExpressionVisitorNodeBuilder(enableDataValidation);

2
src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs

@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Avalonia.Data.Core.Parsers
{
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
class ExpressionVisitorNodeBuilder : ExpressionVisitor
{
private const string MultiDimensionalArrayGetterMethodName = "Get";

3
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ExceptionServices;
using Avalonia.Utilities;
@ -10,6 +11,7 @@ namespace Avalonia.Data.Core.Plugins
public class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public bool Match(object obj, string propertyName)
{
if (obj is AvaloniaObject o)
@ -29,6 +31,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public IPropertyAccessor? Start(WeakReference<object?> reference, string propertyName)
{
_ = reference ?? throw new ArgumentNullException(nameof(reference));

4
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
@ -12,6 +13,7 @@ namespace Avalonia.Data.Core.Plugins
public class DataAnnotationsValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
public bool Match(WeakReference<object?> reference, string memberName)
{
reference.TryGetTarget(out var target);
@ -24,11 +26,13 @@ namespace Avalonia.Data.Core.Plugins
}
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
public IPropertyAccessor Start(WeakReference<object?> reference, string name, IPropertyAccessor inner)
{
return new Accessor(reference, name, inner);
}
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
private sealed class Accessor : DataValidationBase
{
private readonly ValidationContext? _context;

3
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace Avalonia.Data.Core.Plugins
@ -9,9 +10,11 @@ namespace Avalonia.Data.Core.Plugins
public class ExceptionValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
public bool Match(WeakReference<object?> reference, string memberName) => true;
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
public IPropertyAccessor Start(WeakReference<object?> reference, string name, IPropertyAccessor inner)
{
return new Validator(reference, name, inner);

3
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Data.Core.Plugins
{
@ -13,6 +14,7 @@ namespace Avalonia.Data.Core.Plugins
/// <param name="reference">A weak reference to the object.</param>
/// <param name="memberName">The name of the member to validate.</param>
/// <returns>True if the plugin can handle the object; otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
bool Match(WeakReference<object?> reference, string memberName);
/// <summary>
@ -25,6 +27,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
IPropertyAccessor Start(WeakReference<object?> reference,
string propertyName,
IPropertyAccessor inner);

3
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Data.Core.Plugins
{
@ -14,6 +15,7 @@ namespace Avalonia.Data.Core.Plugins
/// <param name="obj">The object.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>True if the plugin can handle the property on the object; otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
bool Match(object obj, string propertyName);
/// <summary>
@ -25,6 +27,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
IPropertyAccessor? Start(WeakReference<object?> reference,
string propertyName);
}

3
src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Data.Core.Plugins
{
@ -12,6 +13,7 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
bool Match(WeakReference<object?> reference);
/// <summary>
@ -21,6 +23,7 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
IObservable<object?> Start(WeakReference<object?> reference);
}
}

3
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Utilities;
@ -18,6 +19,7 @@ namespace Avalonia.Data.Core.Plugins
);
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
public bool Match(WeakReference<object?> reference, string memberName)
{
reference.TryGetTarget(out var target);
@ -26,6 +28,7 @@ namespace Avalonia.Data.Core.Plugins
}
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]
public IPropertyAccessor Start(WeakReference<object?> reference, string name, IPropertyAccessor accessor)
{
return new Validator(reference, name, accessor);

10
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Avalonia.Utilities;
@ -17,6 +17,7 @@ namespace Avalonia.Data.Core.Plugins
new Dictionary<(Type, string), PropertyInfo?>();
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null;
/// <summary>
@ -28,6 +29,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public IPropertyAccessor? Start(WeakReference<object?> reference, string propertyName)
{
_ = reference ?? throw new ArgumentNullException(nameof(reference));
@ -52,7 +54,8 @@ namespace Avalonia.Data.Core.Plugins
private const BindingFlags PropertyBindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName)
{
if (instance is IReflectableType reflectableType && instance is not Type)
@ -70,7 +73,8 @@ namespace Avalonia.Data.Core.Plugins
return propertyInfo;
}
private PropertyInfo? TryFindAndCacheProperty(Type type, string propertyName)
private PropertyInfo? TryFindAndCacheProperty(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, string propertyName)
{
PropertyInfo? found = null;

9
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
@ -10,8 +11,10 @@ namespace Avalonia.Data.Core.Plugins
private readonly Dictionary<(Type, string), MethodInfo?> _methodLookup =
new Dictionary<(Type, string), MethodInfo?>();
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public bool Match(object obj, string methodName) => GetFirstMethodWithName(obj.GetType(), methodName) != null;
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public IPropertyAccessor? Start(WeakReference<object?> reference, string methodName)
{
_ = reference ?? throw new ArgumentNullException(nameof(reference));
@ -34,7 +37,8 @@ namespace Avalonia.Data.Core.Plugins
}
}
private MethodInfo? GetFirstMethodWithName(Type type, string methodName)
private MethodInfo? GetFirstMethodWithName(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, string methodName)
{
var key = (type, methodName);
@ -46,7 +50,8 @@ namespace Avalonia.Data.Core.Plugins
return methodInfo;
}
private MethodInfo? TryFindAndCacheMethod(Type type, string methodName)
private MethodInfo? TryFindAndCacheMethod(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, string methodName)
{
MethodInfo? found = null;

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

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
@ -8,6 +9,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
public class ObservableStreamPlugin : IStreamPlugin
{
static MethodInfo? observableSelect;
@ -17,6 +19,7 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public virtual bool Match(WeakReference<object?> reference)
{
reference.TryGetTarget(out var target);
@ -33,6 +36,7 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public virtual IObservable<object?> Start(WeakReference<object?> reference)
{
if (!reference.TryGetTarget(out var target) || target is null)
@ -65,6 +69,7 @@ namespace Avalonia.Data.Core.Plugins
new object[] { target, box })!;
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static MethodInfo GetObservableSelect(Type source)
{
return GetObservableSelect().MakeGenericMethod(source, typeof(object));

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

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
@ -9,6 +10,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
public class TaskStreamPlugin : IStreamPlugin
{
/// <summary>
@ -16,12 +18,13 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public virtual bool Match(WeakReference<object?> reference)
{
reference.TryGetTarget(out var target);
return target is Task;
}
}
/// <summary>
/// Starts producing output based on the specified value.
@ -30,6 +33,7 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
public virtual IObservable<object?> Start(WeakReference<object?> reference)
{
reference.TryGetTarget(out var target);
@ -59,6 +63,7 @@ namespace Avalonia.Data.Core.Plugins
return Observable.Empty<object?>();
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static IObservable<object?> HandleCompleted(Task task)
{
var resultProperty = task.GetType().GetRuntimeProperty("Result");

3
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -1,9 +1,10 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core
{
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
public class PropertyAccessorNode : SettableNode
{
private readonly bool _enableValidation;

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

@ -1,9 +1,11 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core
{
[RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)]
public class StreamNode : ExpressionNode
{
private IStreamPlugin? _customPlugin = null;

30
src/Avalonia.Base/Diagnostics/TrimmingMessages.cs

@ -0,0 +1,30 @@
namespace Avalonia;
internal static class TrimmingMessages
{
public const string ImplicitTypeConvertionSupressWarningMessage = "Implicit convertion methods might be removed by the linker. We don't have a reliable way to prevent it, except converting everything in compile time when possible.";
public const string ImplicitTypeConvertionRequiresUnreferencedCodeMessage = "Implicit convertion methods are required for type conversion.";
public const string TypeConvertionSupressWarningMessage = "Convertion methods might be removed by the linker. We don't have a reliable way to prevent it, except converting everything in compile time when possible.";
public const string TypeConvertionRequiresUnreferencedCodeMessage = "Convertion methods are required for type conversion, including op_Implicit, op_Explicit, Parse and TypeConverter.";
public const string ReflectionBindingRequiresUnreferencedCodeMessage = "BindingExpression and ReflectionBinding heavily use reflection. Consider using CompiledBindings instead.";
public const string ReflectionBindingSupressWarningMessage = "BindingExpression and ReflectionBinding internal heavily use reflection.";
public const string CompiledBindingSafeSupressWarningMessage = "CompiledBinding preserves members used in the expression tree.";
public const string ExpressionNodeRequiresUnreferencedCodeMessage = "ExpressionNode might require unreferenced code.";
public const string ExpressionSafeSupressWarningMessage = "Typed Expressions preserves members used in the expression tree.";
public const string SelectorsParseRequiresUnreferencedCodeMessage = "Selectors runtime parser might require unreferenced code. Consider using stronly typed selectors factory with 'new Style(s => s.OfType<Button>())' syntax.";
public const string PropertyAccessorsRequiresUnreferencedCodeMessage = "PropertyAccessors might require unreferenced code.";
public const string DataValidationPluginRequiresUnreferencedCodeMessage = "DataValidationPlugin might require unreferenced code.";
public const string StreamPluginRequiresUnreferencedCodeMessage = "StreamPlugin might require unreferenced code.";
public const string StyleResourceIncludeRequiresUnreferenceCodeMessage = "StyleInclude and ResourceInclude use AvaloniaXamlLoader.Load which dynamically loads referenced assembly with Avalonia resources. Note, StyleInclude and ResourceInclude defined in XAML are resolved compile time and are safe with trimming and AOT.";
public const string AvaloniaXamlLoaderRequiresUnreferenceCodeMessage = "AvaloniaXamlLoader.Load(uri, baseUri) dynamically loads referenced assembly with Avalonia resources.";
public const string XamlTypeResolvedRequiresUnreferenceCodeMessage = "XamlTypeResolver might require unreferenced code.";
public const string IgnoreNativeAotSupressWarningMessage = "This method is not supported by NativeAOT.";
}

152
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@ -0,0 +1,152 @@
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
public class PullGestureRecognizer : StyledElement, IGestureRecognizer
{
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private Point _initialPosition;
private int _gestureId;
private IPointer? _tracking;
private PullDirection _pullDirection;
/// <summary>
/// Defines the <see cref="PullDirection"/> property.
/// </summary>
public static readonly DirectProperty<PullGestureRecognizer, PullDirection> PullDirectionProperty =
AvaloniaProperty.RegisterDirect<PullGestureRecognizer, PullDirection>(
nameof(PullDirection),
o => o.PullDirection,
(o, v) => o.PullDirection = v);
public PullDirection PullDirection
{
get => _pullDirection;
set => SetAndRaise(PullDirectionProperty, ref _pullDirection, value);
}
public PullGestureRecognizer(PullDirection pullDirection)
{
PullDirection = pullDirection;
}
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
_target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
_target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
{
if (_tracking == pointer)
{
EndPull();
}
}
public void PointerMoved(PointerEventArgs e)
{
if (_tracking == e.Pointer && _target is Visual visual)
{
var currentPosition = e.GetPosition(visual);
_actions!.Capture(e.Pointer, this);
Vector delta = default;
switch (PullDirection)
{
case PullDirection.TopToBottom:
if (currentPosition.Y > _initialPosition.Y)
{
delta = new Vector(0, currentPosition.Y - _initialPosition.Y);
}
break;
case PullDirection.BottomToTop:
if (currentPosition.Y < _initialPosition.Y)
{
delta = new Vector(0, _initialPosition.Y - currentPosition.Y);
}
break;
case PullDirection.LeftToRight:
if (currentPosition.X > _initialPosition.X)
{
delta = new Vector(currentPosition.X - _initialPosition.X, 0);
}
break;
case PullDirection.RightToLeft:
if (currentPosition.X < _initialPosition.X)
{
delta = new Vector(_initialPosition.X - currentPosition.X, 0);
}
break;
}
_target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection));
}
}
public void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
var position = e.GetPosition(visual);
var canPull = false;
var bounds = visual.Bounds;
switch (PullDirection)
{
case PullDirection.TopToBottom:
canPull = position.Y < bounds.Height * 0.1;
break;
case PullDirection.BottomToTop:
canPull = position.Y > bounds.Height - (bounds.Height * 0.1);
break;
case PullDirection.LeftToRight:
canPull = position.X < bounds.Width * 0.1;
break;
case PullDirection.RightToLeft:
canPull = position.X > bounds.Width - (bounds.Width * 0.1);
break;
}
if (canPull)
{
_gestureId = PullGestureEventArgs.GetNextFreeId();
_tracking = e.Pointer;
_initialPosition = position;
}
}
}
public void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer)
{
EndPull();
}
}
private void EndPull()
{
_tracking = null;
_initialPosition = default;
_target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
}
}
}

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

@ -46,6 +46,14 @@ namespace Avalonia.Input
private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint;
public static readonly RoutedEvent<PullGestureEventArgs> PullGestureEvent =
RoutedEvent.Register<PullGestureEventArgs>(
"PullGesture", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<PullGestureEndedEventArgs> PullGestureEndedEvent =
RoutedEvent.Register<PullGestureEndedEventArgs>(
"PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
static Gestures()
{
InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed);

43
src/Avalonia.Base/Input/PullGestureEventArgs.cs

@ -0,0 +1,43 @@
using System;
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class PullGestureEventArgs : RoutedEventArgs
{
public int Id { get; }
public Vector Delta { get; }
public PullDirection PullDirection { get; }
private static int _nextId = 1;
internal static int GetNextFreeId() => _nextId++;
public PullGestureEventArgs(int id, Vector delta, PullDirection pullDirection) : base(Gestures.PullGestureEvent)
{
Id = id;
Delta = delta;
PullDirection = pullDirection;
}
}
public class PullGestureEndedEventArgs : RoutedEventArgs
{
public int Id { get; }
public PullDirection PullDirection { get; }
public PullGestureEndedEventArgs(int id, PullDirection pullDirection) : base(Gestures.PullGestureEndedEvent)
{
Id = id;
PullDirection = pullDirection;
}
}
public enum PullDirection
{
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft
}
}

2
src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs

@ -1,9 +1,11 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore
{
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
internal class DirectUntypedBindingObserver<T> : IObserver<object?>,
IDisposable
{

2
src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs

@ -7,6 +7,7 @@ namespace Avalonia.PropertyStore
{
internal static class UntypedValueUtils
{
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
public static BindingValue<T> ConvertAndValidate<T>(
object? value,
Type targetType,
@ -23,6 +24,7 @@ namespace Avalonia.PropertyStore
return v;
}
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
public static bool TryConvertAndValidate<T>(
StyledPropertyBase<T> property,
object? value,

67
src/Avalonia.Base/StyledElement.cs

@ -81,6 +81,7 @@ namespace Avalonia
private Styles? _styles;
private bool _stylesApplied;
private bool _themeApplied;
private bool _templatedParentThemeApplied;
private AvaloniaObject? _templatedParent;
private bool _dataContextUpdating;
private ControlTheme? _implicitTheme;
@ -375,6 +376,12 @@ namespace Avalonia
_themeApplied = true;
}
if (!_templatedParentThemeApplied)
{
ApplyTemplatedParentControlTheme();
_templatedParentThemeApplied = true;
}
if (!_stylesApplied)
{
ApplyStyles(this);
@ -613,26 +620,38 @@ namespace Avalonia
base.OnPropertyChanged(change);
if (change.Property == ThemeProperty)
{
OnControlThemeChanged();
_themeApplied = false;
}
}
private protected virtual void OnControlThemeChanged()
{
var values = GetValueStore();
values.BeginStyling();
try { values.RemoveFrames(FrameType.Theme); }
finally { values.EndStyling(); }
try
{
values.RemoveFrames(FrameType.Theme);
}
finally
{
values.EndStyling();
_themeApplied = false;
}
}
internal virtual void OnTemplatedParentControlThemeChanged()
{
var values = GetValueStore();
values.BeginStyling();
try { values.RemoveFrames(FrameType.TemplatedParentTheme); }
finally { values.EndStyling(); }
try
{
values.RemoveFrames(FrameType.TemplatedParentTheme);
}
finally
{
values.EndStyling();
_templatedParentThemeApplied = false;
}
}
internal ControlTheme? GetEffectiveTheme()
@ -743,13 +762,13 @@ namespace Avalonia
private void ApplyControlTheme()
{
var theme = GetEffectiveTheme();
if (theme is not null)
if (GetEffectiveTheme() is { } theme)
ApplyControlTheme(theme, FrameType.Theme);
}
if (TemplatedParent is StyledElement styleableParent &&
styleableParent.GetEffectiveTheme() is { } parentTheme)
private void ApplyTemplatedParentControlTheme()
{
if ((TemplatedParent as StyledElement)?.GetEffectiveTheme() is { } parentTheme)
{
ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme);
}
@ -793,6 +812,28 @@ namespace Avalonia
ApplyStyle(child, host, type);
}
private void ReevaluateImplicitTheme()
{
// We only need to check if the theme has changed when Theme isn't set (i.e. when we
// have an implicit theme).
if (Theme is not null)
return;
// Refetch the implicit theme.
var oldImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
_implicitTheme = null;
GetEffectiveTheme();
var newImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
// If the implicit theme has changed, detach the existing theme.
if (newImplicitTheme != oldImplicitTheme)
{
OnControlThemeChanged();
_themeApplied = false;
}
}
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
if (this.GetLogicalParent() == null && !(this is ILogicalRoot))
@ -811,6 +852,7 @@ namespace Avalonia
{
_logicalRoot = e.Root;
ReevaluateImplicitTheme();
ApplyStyling();
NotifyResourcesChanged(propagate: false);
@ -835,7 +877,6 @@ namespace Avalonia
if (_logicalRoot != null)
{
_logicalRoot = null;
_implicitTheme = null;
InvalidateStyles(recurse: false);
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);

2
src/Avalonia.Base/StyledPropertyBase.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Avalonia.Data;
using Avalonia.PropertyStore;
@ -206,6 +207,7 @@ namespace Avalonia
}
/// <inheritdoc/>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
internal override IDisposable? RouteSetValue(
AvaloniaObject target,
object? value,

5
src/Avalonia.Base/Styling/PropertyEqualsSelector.cs

@ -1,11 +1,10 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Styling.Activators;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
@ -92,6 +91,8 @@ namespace Avalonia.Styling
protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous;
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)]
[UnconditionalSuppressMessage("Trimming", "IL2067", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)]
internal static bool Compare(Type propertyType, object? propertyValue, object? value)
{
if (propertyType == typeof(object) &&

2
src/Avalonia.Base/Styling/Setter.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Animation;
using Avalonia.Data;
using Avalonia.Metadata;
@ -64,6 +65,7 @@ namespace Avalonia.Styling
void IValueEntry.Unsubscribe() { }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
ISetterInstance ISetter.Instance(IStyleInstance instance, StyledElement target)
{
if (target is not AvaloniaObject ao)

3
src/Avalonia.Base/Styling/StyleInstance.cs

@ -70,6 +70,9 @@ namespace Avalonia.Styling
_animationTrigger ??= new Subject<bool>();
foreach (var animation in _animations)
animation.Apply(animatable, null, _animationTrigger);
if (_activator is null)
_animationTrigger.OnNext(true);
}
}

3
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@ -4,6 +4,7 @@ using System.IO;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Utilities
{
@ -33,6 +34,7 @@ namespace Avalonia.Utilities
return entries;
}
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries)
{
new BinaryWriter(stream).Write(LastKnownVersion);
@ -43,6 +45,7 @@ namespace Avalonia.Utilities
});
}
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
public static byte[] Create(Dictionary<string, byte[]> data)
{
var sources = data.ToList();

13
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
@ -125,6 +126,7 @@ namespace Avalonia.Utilities
/// <param name="culture">The culture to use.</param>
/// <param name="result">If successful, contains the convert value.</param>
/// <returns>True if the cast was successful, otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
public static bool TryConvert(Type to, object? value, CultureInfo? culture, out object? result)
{
if (value == null)
@ -150,7 +152,7 @@ namespace Avalonia.Utilities
if (toUnderl == typeof(string))
{
result = Convert.ToString(value, culture);
result = Convert.ToString(value, culture)!;
return true;
}
@ -244,6 +246,7 @@ namespace Avalonia.Utilities
/// <param name="value">The value to convert.</param>
/// <param name="result">If successful, contains the converted value.</param>
/// <returns>True if the convert was successful, otherwise false.</returns>
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public static bool TryConvertImplicit(Type to, object? value, out object? result)
{
if (value == null)
@ -306,6 +309,7 @@ namespace Avalonia.Utilities
/// <param name="type">The type to convert to..</param>
/// <param name="culture">The culture to use.</param>
/// <returns>A value of <paramref name="type"/>.</returns>
[RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
public static object? ConvertOrDefault(object? value, Type type, CultureInfo culture)
{
return TryConvert(type, value, culture, out var result) ? result : Default(type);
@ -318,11 +322,13 @@ namespace Avalonia.Utilities
/// <param name="value">The value to convert.</param>
/// <param name="type">The type to convert to.</param>
/// <returns>A value of <paramref name="type"/>.</returns>
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public static object? ConvertImplicitOrDefault(object? value, Type type)
{
return TryConvertImplicit(type, value, out var result) ? result : Default(type);
}
[RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
public static T ConvertImplicit<T>(object value)
{
if (TryConvertImplicit(typeof(T), value, out var result))
@ -339,6 +345,7 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The default value.</returns>
[UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "We don't care about public ctors for the value types, and always return null for the ref types.")]
public static object? Default(Type type)
{
if (type.IsValueType)
@ -391,7 +398,9 @@ namespace Avalonia.Utilities
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
private static MethodInfo? FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType)
private static MethodInfo? FindTypeConversionOperatorMethod(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type fromType,
Type toType, OperatorType operatorType)
{
const string implicitName = "op_Implicit";
const string explicitName = "op_Explicit";

8
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
@ -20,7 +21,8 @@ namespace Avalonia.Utilities
/// <param name="target">The event source.</param>
/// <param name="eventName">The name of the event.</param>
/// <param name="subscriber">The subscriber.</param>
public static void Subscribe<TTarget, TEventArgs, TSubscriber>(TTarget target, string eventName, EventHandler<TEventArgs> subscriber)
public static void Subscribe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] TTarget, TEventArgs, TSubscriber>(
TTarget target, string eventName, EventHandler<TEventArgs> subscriber)
where TEventArgs : EventArgs where TSubscriber : class
{
_ = target ?? throw new ArgumentNullException(nameof(target));
@ -92,7 +94,9 @@ namespace Avalonia.Utilities
private static Dictionary<MethodInfo, CallerDelegate> s_Callers =
new Dictionary<MethodInfo, CallerDelegate>();
public Subscription(SubscriptionDic<T, TSubscriber> sdic, Type targetType, object target, string eventName)
public Subscription(SubscriptionDic<T, TSubscriber> sdic,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType,
object target, string eventName)
{
_sdic = sdic;
_target = target;

90
src/Avalonia.Base/Visual.cs

@ -89,6 +89,14 @@ namespace Avalonia
public static readonly StyledProperty<RelativePoint> RenderTransformOriginProperty =
AvaloniaProperty.Register<Visual, RelativePoint>(nameof(RenderTransformOrigin), defaultValue: RelativePoint.Center);
/// <summary>
/// Defines the <see cref="FlowDirection"/> property.
/// </summary>
public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
AvaloniaProperty.RegisterAttached<Visual, Visual, FlowDirection>(
nameof(FlowDirection),
inherits: true);
/// <summary>
/// Defines the <see cref="VisualParent"/> property.
/// </summary>
@ -263,6 +271,15 @@ namespace Avalonia
set { SetValue(RenderTransformOriginProperty, value); }
}
/// <summary>
/// Gets or sets the text flow direction.
/// </summary>
public FlowDirection FlowDirection
{
get => GetValue(FlowDirectionProperty);
set => SetValue(FlowDirectionProperty, value);
}
/// <summary>
/// Gets or sets the Z index of the control.
/// </summary>
@ -306,6 +323,36 @@ namespace Avalonia
/// </summary>
internal Visual? VisualParent => _visualParent;
/// <summary>
/// Gets a value indicating whether control bypass FlowDirecton policies.
/// </summary>
/// <remarks>
/// Related to FlowDirection system and returns false as default, so if
/// <see cref="FlowDirection"/> is RTL then control will get a mirror presentation.
/// For controls that want to avoid this behavior, override this property and return true.
/// </remarks>
protected virtual bool BypassFlowDirectionPolicies => false;
/// <summary>
/// Gets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="visual">The control.</param>
/// <returns>The flow direction.</returns>
public static FlowDirection GetFlowDirection(Visual visual)
{
return visual.GetValue(FlowDirectionProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="visual">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFlowDirection(Visual visual, FlowDirection value)
{
visual.SetValue(FlowDirectionProperty, value);
}
/// <summary>
/// Invalidates the visual and queues a repaint.
/// </summary>
@ -387,6 +434,22 @@ namespace Avalonia
}
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == FlowDirectionProperty)
{
InvalidateMirrorTransform();
foreach (var child in VisualChildren)
{
child.InvalidateMirrorTransform();
}
}
}
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
@ -682,5 +745,32 @@ namespace Avalonia
visual.SetVisualParent(parent);
}
}
/// <summary>
/// Computes the <see cref="HasMirrorTransform"/> value according to the
/// <see cref="FlowDirection"/> and <see cref="BypassFlowDirectionPolicies"/>
/// </summary>
public virtual void InvalidateMirrorTransform()
{
var flowDirection = this.FlowDirection;
var parentFlowDirection = FlowDirection.LeftToRight;
bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies;
bool parentBypassFlowDirectionPolicies = false;
var parent = VisualParent;
if (parent != null)
{
parentFlowDirection = parent.FlowDirection;
parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies;
}
bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies;
bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies;
bool shouldApplyMirrorTransform = thisShouldBeMirrored != parentShouldBeMirrored;
HasMirrorTransform = shouldApplyMirrorTransform;
}
}
}

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -6,7 +6,7 @@
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
<DefineConstants>$(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL</DefineConstants>
<CopyLocalLockFileAssemblies Condition="$(TargetFramework) == 'netstandard2.0'">true</CopyLocalLockFileAssemblies>
<NoWarn>NU1605;CS8632</NoWarn>
<NoWarn>$(NoWarn);NU1605;CS8632</NoWarn>
</PropertyGroup>
<!--Disable Net Perf. analyzer for submodule to avoid commit issue -->
@ -111,6 +111,7 @@
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
<Compile Include="..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\**\obj\**\*.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
<PackageReference Include="Mono.Cecil" Version="0.11.4" />

1
src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj

@ -21,6 +21,7 @@
<Import Project="..\..\build\BuildTargets.targets" />
<!--<Import Project="..\..\build\ApiDiff.props" />-->
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
<ItemGroup Label="InternalsVisibleTo">

4
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -6,8 +6,8 @@
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z</StreamGeometry>
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />

50
src/Avalonia.Controls/AppBuilderBase.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Linq;
using Avalonia.Controls.ApplicationLifetimes;
@ -162,13 +163,6 @@ namespace Avalonia.Controls
return Self;
}
/// <summary>
/// Specifies a windowing subsystem to use.
/// </summary>
/// <param name="dll">The dll in which to look for subsystem.</param>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
public TAppBuilder UseWindowingSubsystem(string dll) => UseWindowingSubsystem(GetInitializer(dll), dll.Replace("Avalonia.", string.Empty));
/// <summary>
/// Specifies a rendering subsystem to use.
/// </summary>
@ -182,50 +176,8 @@ namespace Avalonia.Controls
return Self;
}
/// <summary>
/// Specifies a rendering subsystem to use.
/// </summary>
/// <param name="dll">The dll in which to look for subsystem.</param>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
public TAppBuilder UseRenderingSubsystem(string dll) => UseRenderingSubsystem(GetInitializer(dll));
static Action GetInitializer(string assemblyName) => () =>
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform";
var platformClassFullName = assemblyName + "." + platformClassName;
var platformClass = assembly.GetType(platformClassFullName);
var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes);
init!.Invoke(null, null);
};
public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
protected virtual bool CheckSetup => true;
/// <summary>
/// Searches and initiates modules included with <see cref="ExportAvaloniaModuleAttribute"/> attribute.
/// </summary>
private void SetupAvaloniaModules()
{
var moduleInitializers = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from attribute in assembly.GetCustomAttributes<ExportAvaloniaModuleAttribute>()
where string.IsNullOrEmpty(attribute.ForWindowingSubsystem)
|| attribute.ForWindowingSubsystem == WindowingSubsystemName
where string.IsNullOrEmpty(attribute.ForRenderingSubsystem)
|| attribute.ForRenderingSubsystem == RenderingSubsystemName
group attribute by attribute.Name into exports
select (from export in exports
orderby export.ForWindowingSubsystem.Length descending
orderby export.ForRenderingSubsystem.Length descending
select export).First().ModuleType into moduleType
select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors
where constructor.GetParameters().Length == 0 && !constructor.IsStatic
select constructor).Single() into constructor
select (Action)(() => constructor.Invoke(Array.Empty<object>()));
Delegate.Combine(moduleInitializers.ToArray())!.DynamicInvoke();
}
/// <summary>
/// Configures platform-specific options
/// </summary>

1
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -10,6 +10,7 @@
<Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
<ItemGroup Label="InternalsVisibleTo">

5
src/Avalonia.Controls/ComboBox.cs

@ -454,10 +454,9 @@ namespace Avalonia.Controls
{
if (SelectionBoxItem is Rectangle rectangle)
{
if ((rectangle.Fill as VisualBrush)?.Visual is Control content)
if ((rectangle.Fill as VisualBrush)?.Visual is Visual content)
{
var flowDirection = (((Visual)content!).VisualParent as Control)?.FlowDirection ??
FlowDirection.LeftToRight;
var flowDirection = content.VisualParent?.FlowDirection ?? FlowDirection.LeftToRight;
rectangle.FlowDirection = flowDirection;
}
}

85
src/Avalonia.Controls/Control.cs

@ -91,13 +91,6 @@ namespace Avalonia.Controls
RoutedEvent.Register<Control, SizeChangedEventArgs>(
nameof(SizeChanged), RoutingStrategies.Direct);
/// <summary>
/// Defines the <see cref="FlowDirection"/> property.
/// </summary>
public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(
nameof(FlowDirection),
inherits: true);
// Note the following:
// _loadedQueue :
@ -170,15 +163,6 @@ namespace Avalonia.Controls
get => GetValue(TagProperty);
set => SetValue(TagProperty, value);
}
/// <summary>
/// Gets or sets the text flow direction.
/// </summary>
public FlowDirection FlowDirection
{
get => GetValue(FlowDirectionProperty);
set => SetValue(FlowDirectionProperty, value);
}
/// <summary>
/// Occurs when the user has completed a context input gesture, such as a right-click.
@ -229,39 +213,9 @@ namespace Avalonia.Controls
public new Control? Parent => (Control?)base.Parent;
/// <summary>
/// Gets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The flow direction.</returns>
public static FlowDirection GetFlowDirection(Control control)
{
return control.GetValue(FlowDirectionProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFlowDirection(Control control, FlowDirection value)
{
control.SetValue(FlowDirectionProperty, value);
}
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary>
/// Gets a value indicating whether control bypass FlowDirecton policies.
/// </summary>
/// <remarks>
/// Related to FlowDirection system and returns false as default, so if
/// <see cref="FlowDirection"/> is RTL then control will get a mirror presentation.
/// For controls that want to avoid this behavior, override this property and return true.
/// </remarks>
protected virtual bool BypassFlowDirectionPolicies => false;
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter)
{
@ -571,45 +525,6 @@ namespace Avalonia.Controls
RaiseEvent(sizeChangedEventArgs);
}
}
else if (change.Property == FlowDirectionProperty)
{
InvalidateMirrorTransform();
foreach (var visual in VisualChildren)
{
if (visual is Control child)
{
child.InvalidateMirrorTransform();
}
}
}
}
/// <summary>
/// Computes the <see cref="Visual.HasMirrorTransform"/> value according to the
/// <see cref="FlowDirection"/> and <see cref="BypassFlowDirectionPolicies"/>
/// </summary>
public virtual void InvalidateMirrorTransform()
{
var flowDirection = this.FlowDirection;
var parentFlowDirection = FlowDirection.LeftToRight;
bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies;
bool parentBypassFlowDirectionPolicies = false;
var parent = this.VisualParent as Control;
if (parent != null)
{
parentFlowDirection = parent.FlowDirection;
parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies;
}
bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies;
bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies;
bool shouldApplyMirrorTransform = thisShouldBeMirrored != parentShouldBeMirrored;
HasMirrorTransform = shouldApplyMirrorTransform;
}
}
}

12
src/Avalonia.Controls/NativeMenuBar.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
@ -21,7 +22,16 @@ namespace Avalonia.Controls
item.Click -= OnMenuItemClick;
});
}
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenu))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItem))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemBase))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemSeparator))]
public NativeMenuBar()
{
}
public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable)
{
menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable);

2
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -588,7 +588,7 @@ namespace Avalonia.Controls.Primitives
}
else if (e.Key == Key.Space || e.Key == Key.Enter)
{
e.Handled = UpdateSelectionFromEventSource(
UpdateSelectionFromEventSource(
e.Source,
true,
e.KeyModifiers.HasFlag(KeyModifiers.Shift),

36
src/Avalonia.Controls/PullToRefresh/RefreshCompletionDeferral.cs

@ -0,0 +1,36 @@
using System;
using System.Threading;
namespace Avalonia.Controls
{
/// <summary>
/// Deferral class for notify that a work done in RefreshRequested event is done.
/// </summary>
public class RefreshCompletionDeferral
{
private Action _deferredAction;
private int _deferCount;
public RefreshCompletionDeferral(Action deferredAction)
{
_deferredAction = deferredAction;
}
public void Complete()
{
Interlocked.Decrement(ref _deferCount);
if (_deferCount == 0)
{
_deferredAction?.Invoke();
}
}
public RefreshCompletionDeferral Get()
{
Interlocked.Increment(ref _deferCount);
return this;
}
}
}

252
src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs

@ -0,0 +1,252 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.PullToRefresh;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Represents a container control that provides a <see cref="RefreshVisualizer"/> and pull-to-refresh functionality for scrollable content.
/// </summary>
public class RefreshContainer : ContentControl
{
internal const int DefaultPullDimensionSize = 100;
private bool _hasDefaultRefreshInfoProviderAdapter;
private ScrollViewerIRefreshInfoProviderAdapter? _refreshInfoProviderAdapter;
private RefreshInfoProvider? _refreshInfoProvider;
private IDisposable? _visualizerSizeSubscription;
private Grid? _visualizerPresenter;
private RefreshVisualizer? _refreshVisualizer;
private bool _hasDefaultRefreshVisualizer;
/// <summary>
/// Defines the <see cref="RefreshRequested"/> event.
/// </summary>
public static readonly RoutedEvent<RefreshRequestedEventArgs> RefreshRequestedEvent =
RoutedEvent.Register<RefreshContainer, RefreshRequestedEventArgs>(nameof(RefreshRequested), RoutingStrategies.Bubble);
internal static readonly DirectProperty<RefreshContainer, ScrollViewerIRefreshInfoProviderAdapter?> RefreshInfoProviderAdapterProperty =
AvaloniaProperty.RegisterDirect<RefreshContainer, ScrollViewerIRefreshInfoProviderAdapter?>(nameof(RefreshInfoProviderAdapter),
(s) => s.RefreshInfoProviderAdapter, (s, o) => s.RefreshInfoProviderAdapter = o);
/// <summary>
/// Defines the <see cref="Visualizer"/> event.
/// </summary>
public static readonly DirectProperty<RefreshContainer, RefreshVisualizer?> VisualizerProperty =
AvaloniaProperty.RegisterDirect<RefreshContainer, RefreshVisualizer?>(nameof(Visualizer),
s => s.Visualizer, (s, o) => s.Visualizer = o);
/// <summary>
/// Defines the <see cref="PullDirection"/> event.
/// </summary>
public static readonly StyledProperty<PullDirection> PullDirectionProperty =
AvaloniaProperty.Register<RefreshContainer, PullDirection>(nameof(PullDirection), PullDirection.TopToBottom);
internal ScrollViewerIRefreshInfoProviderAdapter? RefreshInfoProviderAdapter
{
get => _refreshInfoProviderAdapter; set
{
_hasDefaultRefreshInfoProviderAdapter = false;
SetAndRaise(RefreshInfoProviderAdapterProperty, ref _refreshInfoProviderAdapter, value);
}
}
/// <summary>
/// Gets or sets the <see cref="RefreshVisualizer"/> for this container.
/// </summary>
public RefreshVisualizer? Visualizer
{
get => _refreshVisualizer; set
{
if (_refreshVisualizer != null)
{
_visualizerSizeSubscription?.Dispose();
_refreshVisualizer.RefreshRequested -= Visualizer_RefreshRequested;
}
SetAndRaise(VisualizerProperty, ref _refreshVisualizer, value);
}
}
/// <summary>
/// Gets or sets a value that specifies the direction to pull to initiate a refresh.
/// </summary>
public PullDirection PullDirection
{
get => GetValue(PullDirectionProperty);
set => SetValue(PullDirectionProperty, value);
}
/// <summary>
/// Occurs when an update of the content has been initiated.
/// </summary>
public event EventHandler<RefreshRequestedEventArgs>? RefreshRequested
{
add => AddHandler(RefreshRequestedEvent, value);
remove => RemoveHandler(RefreshRequestedEvent, value);
}
public RefreshContainer()
{
_hasDefaultRefreshInfoProviderAdapter = true;
_refreshInfoProviderAdapter = new ScrollViewerIRefreshInfoProviderAdapter(PullDirection);
RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_visualizerPresenter = e.NameScope.Find<Grid>("PART_RefreshVisualizerPresenter");
if (_refreshVisualizer == null)
{
_hasDefaultRefreshVisualizer = true;
Visualizer = new RefreshVisualizer();
}
else
{
_hasDefaultRefreshVisualizer = false;
RaisePropertyChanged(VisualizerProperty, default, _refreshVisualizer);
}
OnPullDirectionChanged();
}
private void OnVisualizerSizeChanged(Rect obj)
{
if (_hasDefaultRefreshInfoProviderAdapter)
{
_refreshInfoProviderAdapter = new ScrollViewerIRefreshInfoProviderAdapter(PullDirection);
RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
}
}
private void Visualizer_RefreshRequested(object? sender, RefreshRequestedEventArgs e)
{
var ev = new RefreshRequestedEventArgs(e.GetDeferral(), RefreshRequestedEvent);
RaiseEvent(ev);
ev.DecrementCount();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == RefreshInfoProviderAdapterProperty)
{
if (_refreshVisualizer != null)
{
if (_refreshInfoProvider != null)
{
_refreshVisualizer.RefreshInfoProvider = _refreshInfoProvider;
}
else
{
if (RefreshInfoProviderAdapter != null && _refreshVisualizer != null)
{
_refreshInfoProvider = RefreshInfoProviderAdapter?.AdaptFromTree(this, _refreshVisualizer.Bounds.Size);
if (_refreshInfoProvider != null)
{
_refreshVisualizer.RefreshInfoProvider = _refreshInfoProvider;
RefreshInfoProviderAdapter?.SetAnimations(_refreshVisualizer);
}
}
}
}
}
else if (change.Property == VisualizerProperty)
{
if (_visualizerPresenter != null)
{
_visualizerPresenter.Children.Clear();
if (_refreshVisualizer != null)
{
_visualizerPresenter.Children.Add(_refreshVisualizer);
}
}
if (_refreshVisualizer != null)
{
_refreshVisualizer.RefreshRequested += Visualizer_RefreshRequested;
_visualizerSizeSubscription = _refreshVisualizer.GetObservable(Control.BoundsProperty).Subscribe(OnVisualizerSizeChanged);
}
}
else if (change.Property == PullDirectionProperty)
{
OnPullDirectionChanged();
}
}
private void OnPullDirectionChanged()
{
if (_visualizerPresenter != null && _refreshVisualizer != null)
{
switch (PullDirection)
{
case PullDirection.TopToBottom:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Top;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Stretch;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.TopToBottom;
_refreshVisualizer.Height = DefaultPullDimensionSize;
_refreshVisualizer.Width = double.NaN;
}
break;
case PullDirection.BottomToTop:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Bottom;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Stretch;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.BottomToTop;
_refreshVisualizer.Height = DefaultPullDimensionSize;
_refreshVisualizer.Width = double.NaN;
}
break;
case PullDirection.LeftToRight:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Stretch;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Left;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.LeftToRight;
_refreshVisualizer.Width = DefaultPullDimensionSize;
_refreshVisualizer.Height = double.NaN;
}
break;
case PullDirection.RightToLeft:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Stretch;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Right;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.RightToLeft;
_refreshVisualizer.Width = DefaultPullDimensionSize;
_refreshVisualizer.Height = double.NaN;
}
break;
}
if (_hasDefaultRefreshInfoProviderAdapter &&
_hasDefaultRefreshVisualizer &&
_refreshVisualizer.Bounds.Height == DefaultPullDimensionSize &&
_refreshVisualizer.Bounds.Width == DefaultPullDimensionSize)
{
_refreshInfoProviderAdapter = new ScrollViewerIRefreshInfoProviderAdapter(PullDirection);
RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
}
}
}
/// <summary>
/// Initiates an update of the content.
/// </summary>
public void RequestRefresh()
{
_refreshVisualizer?.RequestRefresh();
}
}
}

141
src/Avalonia.Controls/PullToRefresh/RefreshInfoProvider.cs

@ -0,0 +1,141 @@
using System;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Rendering.Composition;
namespace Avalonia.Controls.PullToRefresh
{
internal class RefreshInfoProvider : Interactive
{
internal const double DefaultExecutionRatio = 0.8;
private readonly PullDirection _refreshPullDirection;
private readonly Size _refreshVisualizerSize;
private readonly CompositionVisual? _visual;
private bool _isInteractingForRefresh;
private double _interactionRatio;
private bool _entered;
public DirectProperty<RefreshInfoProvider, bool> IsInteractingForRefreshProperty =
AvaloniaProperty.RegisterDirect<RefreshInfoProvider, bool>(nameof(IsInteractingForRefresh),
s => s.IsInteractingForRefresh, (s, o) => s.IsInteractingForRefresh = o);
public DirectProperty<RefreshInfoProvider, double> ExecutionRatioProperty =
AvaloniaProperty.RegisterDirect<RefreshInfoProvider, double>(nameof(ExecutionRatio),
s => s.ExecutionRatio);
public DirectProperty<RefreshInfoProvider, double> InteractionRatioProperty =
AvaloniaProperty.RegisterDirect<RefreshInfoProvider, double>(nameof(InteractionRatio),
s => s.InteractionRatio, (s, o) => s.InteractionRatio = o);
/// <summary>
/// Defines the <see cref="RefreshStarted"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> RefreshStartedEvent =
RoutedEvent.Register<RefreshInfoProvider, RoutedEventArgs>(nameof(RefreshStarted), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="RefreshCompleted"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> RefreshCompletedEvent =
RoutedEvent.Register<RefreshInfoProvider, RoutedEventArgs>(nameof(RefreshCompleted), RoutingStrategies.Bubble);
public bool PeekingMode { get; internal set; }
public bool IsInteractingForRefresh
{
get => _isInteractingForRefresh;
internal set
{
var isInteractingForRefresh = value && !PeekingMode;
if (isInteractingForRefresh != _isInteractingForRefresh)
{
SetAndRaise(IsInteractingForRefreshProperty, ref _isInteractingForRefresh, isInteractingForRefresh);
}
}
}
public double InteractionRatio
{
get => _interactionRatio;
set
{
SetAndRaise(InteractionRatioProperty, ref _interactionRatio, value);
}
}
public double ExecutionRatio
{
get => DefaultExecutionRatio;
}
internal CompositionVisual? Visual => _visual;
public event EventHandler<RoutedEventArgs> RefreshStarted
{
add => AddHandler(RefreshStartedEvent, value);
remove => RemoveHandler(RefreshStartedEvent, value);
}
public event EventHandler<RoutedEventArgs> RefreshCompleted
{
add => AddHandler(RefreshCompletedEvent, value);
remove => RemoveHandler(RefreshCompletedEvent, value);
}
internal void InteractingStateEntered(object? sender, PullGestureEventArgs e)
{
if (!_entered)
{
IsInteractingForRefresh = true;
_entered = true;
}
ValuesChanged(e.Delta);
}
internal void InteractingStateExited(object? sender, PullGestureEndedEventArgs e)
{
IsInteractingForRefresh = false;
_entered = false;
ValuesChanged(default);
}
public RefreshInfoProvider(PullDirection refreshPullDirection, Size? refreshVIsualizerSize, CompositionVisual? visual)
{
_refreshPullDirection = refreshPullDirection;
_refreshVisualizerSize = refreshVIsualizerSize ?? default;
_visual = visual;
}
public void OnRefreshStarted()
{
RaiseEvent(new RoutedEventArgs(RefreshStartedEvent));
}
public void OnRefreshCompleted()
{
RaiseEvent(new RoutedEventArgs(RefreshCompletedEvent));
}
internal void ValuesChanged(Vector value)
{
switch (_refreshPullDirection)
{
case PullDirection.TopToBottom:
case PullDirection.BottomToTop:
InteractionRatio = _refreshVisualizerSize.Height == 0 ? 1 : Math.Min(1, value.Y / _refreshVisualizerSize.Height);
break;
case PullDirection.LeftToRight:
case PullDirection.RightToLeft:
InteractionRatio = _refreshVisualizerSize.Height == 0 ? 1 : Math.Min(1, value.X / _refreshVisualizerSize.Width);
break;
}
}
}
}

42
src/Avalonia.Controls/PullToRefresh/RefreshRequestedEventArgs.cs

@ -0,0 +1,42 @@
using System;
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Provides event data for RefreshRequested events.
/// </summary>
public class RefreshRequestedEventArgs : RoutedEventArgs
{
private RefreshCompletionDeferral _refreshCompletionDeferral;
/// <summary>
/// Gets a deferral object for managing the work done in the RefreshRequested event handler.
/// </summary>
/// <returns>A <see cref="RefreshCompletionDeferral"/> object</returns>
public RefreshCompletionDeferral GetDeferral()
{
return _refreshCompletionDeferral.Get();
}
public RefreshRequestedEventArgs(Action deferredAction, RoutedEvent? routedEvent) : base(routedEvent)
{
_refreshCompletionDeferral = new RefreshCompletionDeferral(deferredAction);
}
public RefreshRequestedEventArgs(RefreshCompletionDeferral completionDeferral, RoutedEvent? routedEvent) : base(routedEvent)
{
_refreshCompletionDeferral = completionDeferral;
}
internal void IncrementCount()
{
_refreshCompletionDeferral?.Get();
}
internal void DecrementCount()
{
_refreshCompletionDeferral?.Complete();
}
}
}

553
src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs

@ -0,0 +1,553 @@
using System;
using System.Numerics;
using System.Reactive.Linq;
using Avalonia.Animation.Easings;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.PullToRefresh;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Animations;
namespace Avalonia.Controls
{
public class RefreshVisualizer : ContentControl
{
private const int DefaultIndicatorSize = 24;
private const float MinimumIndicatorOpacity = 0.4f;
private const float ParallaxPositionRatio = 0.5f;
private double _executingRatio = 0.8;
private RefreshVisualizerState _refreshVisualizerState;
private RefreshInfoProvider? _refreshInfoProvider;
private IDisposable? _isInteractingSubscription;
private IDisposable? _interactionRatioSubscription;
private bool _isInteractingForRefresh;
private Grid? _root;
private Control? _content;
private RefreshVisualizerOrientation _orientation;
private float _startingRotationAngle;
private double _interactionRatio;
private bool _played;
private ScalarKeyFrameAnimation? _rotateAnimation;
private bool IsPullDirectionVertical => PullDirection == PullDirection.TopToBottom || PullDirection == PullDirection.BottomToTop;
private bool IsPullDirectionFar => PullDirection == PullDirection.BottomToTop || PullDirection == PullDirection.RightToLeft;
/// <summary>
/// Defines the <see cref="PullDirection"/> property.
/// </summary>
public static readonly StyledProperty<PullDirection> PullDirectionProperty =
AvaloniaProperty.Register<RefreshVisualizer, PullDirection>(nameof(PullDirection), PullDirection.TopToBottom);
/// <summary>
/// Defines the <see cref="RefreshRequested"/> event.
/// </summary>
public static readonly RoutedEvent<RefreshRequestedEventArgs> RefreshRequestedEvent =
RoutedEvent.Register<RefreshVisualizer, RefreshRequestedEventArgs>(nameof(RefreshRequested), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="RefreshVisualizerState"/> property.
/// </summary>
public static readonly DirectProperty<RefreshVisualizer, RefreshVisualizerState> RefreshVisualizerStateProperty =
AvaloniaProperty.RegisterDirect<RefreshVisualizer, RefreshVisualizerState>(nameof(RefreshVisualizerState),
s => s.RefreshVisualizerState);
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly DirectProperty<RefreshVisualizer, RefreshVisualizerOrientation> OrientationProperty =
AvaloniaProperty.RegisterDirect<RefreshVisualizer, RefreshVisualizerOrientation>(nameof(Orientation),
s => s.Orientation, (s, o) => s.Orientation = o);
/// <summary>
/// Defines the <see cref="RefreshInfoProvider"/> property.
/// </summary>
internal DirectProperty<RefreshVisualizer, RefreshInfoProvider?> RefreshInfoProviderProperty =
AvaloniaProperty.RegisterDirect<RefreshVisualizer, RefreshInfoProvider?>(nameof(RefreshInfoProvider),
s => s.RefreshInfoProvider, (s, o) => s.RefreshInfoProvider = o);
/// <summary>
/// Gets or sets a value that indicates the refresh state of the visualizer.
/// </summary>
protected RefreshVisualizerState RefreshVisualizerState
{
get
{
return _refreshVisualizerState;
}
private set
{
SetAndRaise(RefreshVisualizerStateProperty, ref _refreshVisualizerState, value);
UpdateContent();
}
}
/// <summary>
/// Gets or sets a value that indicates the orientation of the visualizer.
/// </summary>
public RefreshVisualizerOrientation Orientation
{
get
{
return _orientation;
}
set
{
SetAndRaise(OrientationProperty, ref _orientation, value);
}
}
internal PullDirection PullDirection
{
get => GetValue(PullDirectionProperty);
set => SetValue(PullDirectionProperty, value);
}
internal RefreshInfoProvider? RefreshInfoProvider
{
get => _refreshInfoProvider;
set
{
if (_refreshInfoProvider != null)
{
_refreshInfoProvider.RenderTransform = null;
}
SetAndRaise(RefreshInfoProviderProperty, ref _refreshInfoProvider, value);
}
}
/// <summary>
/// Occurs when an update of the content has been initiated.
/// </summary>
public event EventHandler<RefreshRequestedEventArgs>? RefreshRequested
{
add => AddHandler(RefreshRequestedEvent, value);
remove => RemoveHandler(RefreshRequestedEvent, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
this.ClipToBounds = false;
_root = e.NameScope.Find<Grid>("PART_Root");
if (_root != null)
{
_content = Content as Control;
if (_content == null)
{
_content = new PathIcon()
{
Height = DefaultIndicatorSize,
Width = DefaultIndicatorSize,
Name = "PART_Icon"
};
_content.Loaded += (s, e) =>
{
var composition = ElementComposition.GetElementVisual(_content);
var compositor = composition!.Compositor;
composition.Opacity = 0;
var smoothRotationAnimation
= compositor.CreateScalarKeyFrameAnimation();
smoothRotationAnimation.Target = "RotationAngle";
smoothRotationAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
smoothRotationAnimation.Duration = TimeSpan.FromMilliseconds(100);
var opacityAnimation
= compositor.CreateScalarKeyFrameAnimation();
opacityAnimation.Target = "Opacity";
opacityAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
opacityAnimation.Duration = TimeSpan.FromMilliseconds(100);
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
var scaleAnimation
= compositor.CreateVector3KeyFrameAnimation();
scaleAnimation.Target = "Scale";
scaleAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", new LinearEasing());
scaleAnimation.Duration = TimeSpan.FromMilliseconds(100);
var animation = compositor.CreateImplicitAnimationCollection();
animation["RotationAngle"] = smoothRotationAnimation;
animation["Offset"] = offsetAnimation;
animation["Scale"] = scaleAnimation;
animation["Opacity"] = opacityAnimation;
composition.ImplicitAnimations = animation;
UpdateContent();
};
Content = _content;
}
else
{
RaisePropertyChanged(ContentProperty, null, Content, Data.BindingPriority.Style, false);
}
}
OnOrientationChanged();
UpdateContent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
UpdateContent();
}
private void UpdateContent()
{
if (_content != null && _root != null)
{
var root = _root;
var visual = _refreshInfoProvider?.Visual;
var contentVisual = ElementComposition.GetElementVisual(_content);
var visualizerVisual = ElementComposition.GetElementVisual(this);
if (visual != null && contentVisual != null && visualizerVisual != null)
{
contentVisual.CenterPoint = new Vector3((float)(_content.Bounds.Width / 2), (float)(_content.Bounds.Height / 2), 0);
switch (RefreshVisualizerState)
{
case RefreshVisualizerState.Idle:
_played = false;
if(_rotateAnimation != null)
{
_rotateAnimation.IterationBehavior = AnimationIterationBehavior.Count;
_rotateAnimation = null;
}
contentVisual.Opacity = MinimumIndicatorOpacity;
contentVisual.RotationAngle = _startingRotationAngle;
visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3(visualizerVisual.Offset.X, 0, 0) :
new Vector3(0, visualizerVisual.Offset.Y, 0);
visual.Offset = default;
_content.InvalidateMeasure();
break;
case RefreshVisualizerState.Interacting:
_played = false;
contentVisual.Opacity = MinimumIndicatorOpacity;
contentVisual.RotationAngle = (float)(_startingRotationAngle + _interactionRatio * 2 * Math.PI);
Vector3 offset = default;
if (IsPullDirectionVertical)
{
offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
}
else
{
offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
}
visual.Offset = offset;
visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
break;
case RefreshVisualizerState.Pending:
contentVisual.Opacity = 1;
contentVisual.RotationAngle = _startingRotationAngle + (float)(2 * Math.PI);
if (IsPullDirectionVertical)
{
offset = new Vector3(0, (float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
}
else
{
offset = new Vector3((float)(_interactionRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
}
visual.Offset = offset;
visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
if (!_played)
{
_played = true;
var scaleAnimation = contentVisual.Compositor!.CreateVector3KeyFrameAnimation();
scaleAnimation.Target = "Scale";
scaleAnimation.InsertKeyFrame(0.5f, new Vector3(1.5f, 1.5f, 1));
scaleAnimation.InsertKeyFrame(1f, new Vector3(1f, 1f, 1));
scaleAnimation.Duration = TimeSpan.FromSeconds(0.3);
contentVisual.StartAnimation("Scale", scaleAnimation);
}
break;
case RefreshVisualizerState.Refreshing:
_rotateAnimation = contentVisual.Compositor!.CreateScalarKeyFrameAnimation();
_rotateAnimation.Target = "RotationAngle";
_rotateAnimation.InsertKeyFrame(0, _startingRotationAngle, new LinearEasing());
_rotateAnimation.InsertKeyFrame(1, _startingRotationAngle + (float)(2 * Math.PI), new LinearEasing());
_rotateAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
_rotateAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;
_rotateAnimation.Duration = TimeSpan.FromSeconds(0.5);
contentVisual.StartAnimation("RotationAngle", _rotateAnimation);
contentVisual.Opacity = 1;
float translationRatio = (float)(_refreshInfoProvider != null ? (1.0f - _refreshInfoProvider.ExecutionRatio) * ParallaxPositionRatio : 1.0f)
* (IsPullDirectionFar ? -1f : 1f);
if (IsPullDirectionVertical)
{
offset = new Vector3(0, (float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Height), 0);
}
else
{
offset = new Vector3((float)(_executingRatio * (IsPullDirectionFar ? -1 : 1) * root.Bounds.Width), 0, 0);
}
visual.Offset = offset;
contentVisual.Offset += IsPullDirectionVertical ? new Vector3(0, (float)(translationRatio * root.Bounds.Height), 0) :
new Vector3((float)(translationRatio * root.Bounds.Width), 0, 0);
visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3(visualizerVisual.Offset.X, offset.Y, 0) :
new Vector3(offset.X, visualizerVisual.Offset.Y, 0);
break;
case RefreshVisualizerState.Peeking:
contentVisual.Opacity = 1;
contentVisual.RotationAngle = _startingRotationAngle;
break;
}
}
}
}
/// <summary>
/// Initiates an update of the content.
/// </summary>
public void RequestRefresh()
{
RefreshVisualizerState = RefreshVisualizerState.Refreshing;
RefreshInfoProvider?.OnRefreshStarted();
RaiseRefreshRequested();
}
private void RefreshCompleted()
{
RefreshVisualizerState = RefreshVisualizerState.Idle;
RefreshInfoProvider?.OnRefreshCompleted();
}
private void RaiseRefreshRequested()
{
var refreshArgs = new RefreshRequestedEventArgs(RefreshCompleted, RefreshRequestedEvent);
refreshArgs.IncrementCount();
RaiseEvent(refreshArgs);
refreshArgs.DecrementCount();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == RefreshInfoProviderProperty)
{
OnRefreshInfoProviderChanged();
}
else if (change.Property == ContentProperty)
{
if (_root != null && _content != null)
{
_root.Children.Insert(0, _content);
_content.VerticalAlignment = Layout.VerticalAlignment.Center;
_content.HorizontalAlignment = Layout.HorizontalAlignment.Center;
}
UpdateContent();
}
else if (change.Property == OrientationProperty)
{
OnOrientationChanged();
UpdateContent();
}
else if (change.Property == BoundsProperty)
{
switch (PullDirection)
{
case PullDirection.TopToBottom:
RenderTransform = new TranslateTransform(0, -Bounds.Height);
break;
case PullDirection.BottomToTop:
RenderTransform = new TranslateTransform(0, Bounds.Height);
break;
case PullDirection.LeftToRight:
RenderTransform = new TranslateTransform(-Bounds.Width, 0);
break;
case PullDirection.RightToLeft:
RenderTransform = new TranslateTransform(Bounds.Width, 0);
break;
}
UpdateContent();
}
else if(change.Property == PullDirectionProperty)
{
OnOrientationChanged();
UpdateContent();
}
}
private void OnOrientationChanged()
{
switch (_orientation)
{
case RefreshVisualizerOrientation.Auto:
switch (PullDirection)
{
case PullDirection.TopToBottom:
case PullDirection.BottomToTop:
_startingRotationAngle = 0.0f;
break;
case PullDirection.LeftToRight:
_startingRotationAngle = (float)(-Math.PI / 2);
break;
case PullDirection.RightToLeft:
_startingRotationAngle = (float)(Math.PI / 2);
break;
}
break;
case RefreshVisualizerOrientation.Normal:
_startingRotationAngle = 0.0f;
break;
case RefreshVisualizerOrientation.Rotate90DegreesCounterclockwise:
_startingRotationAngle = (float)(Math.PI / 2);
break;
case RefreshVisualizerOrientation.Rotate270DegreesCounterclockwise:
_startingRotationAngle = (float)(-Math.PI / 2);
break;
}
}
private void OnRefreshInfoProviderChanged()
{
_isInteractingSubscription?.Dispose();
_isInteractingSubscription = null;
_interactionRatioSubscription?.Dispose();
_interactionRatioSubscription = null;
if (RefreshInfoProvider != null)
{
_isInteractingSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.IsInteractingForRefreshProperty)
.Subscribe(InteractingForRefreshObserver);
_interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty)
.Subscribe(InteractionRatioObserver);
var visual = RefreshInfoProvider.Visual;
_executingRatio = RefreshInfoProvider.ExecutionRatio;
}
else
{
_executingRatio = 1;
}
}
private void InteractionRatioObserver(double obj)
{
var wasAtZero = _interactionRatio == 0.0;
_interactionRatio = obj;
if (_isInteractingForRefresh)
{
if (RefreshVisualizerState == RefreshVisualizerState.Idle)
{
if (wasAtZero)
{
if (_interactionRatio > _executingRatio)
{
RefreshVisualizerState = RefreshVisualizerState.Pending;
}
else if (_interactionRatio > 0)
{
RefreshVisualizerState = RefreshVisualizerState.Interacting;
}
}
else if (_interactionRatio > 0)
{
RefreshVisualizerState = RefreshVisualizerState.Peeking;
}
}
else if (RefreshVisualizerState == RefreshVisualizerState.Interacting)
{
if (_interactionRatio <= 0)
{
RefreshVisualizerState = RefreshVisualizerState.Idle;
}
else if (_interactionRatio > _executingRatio)
{
RefreshVisualizerState = RefreshVisualizerState.Pending;
}
else
{
UpdateContent();
}
}
else if (RefreshVisualizerState == RefreshVisualizerState.Pending)
{
if (_interactionRatio <= _executingRatio)
{
RefreshVisualizerState = RefreshVisualizerState.Interacting;
}
else if (_interactionRatio <= 0)
{
RefreshVisualizerState = RefreshVisualizerState.Idle;
}
else
{
UpdateContent();
}
}
}
else
{
if (RefreshVisualizerState != RefreshVisualizerState.Refreshing)
{
if (_interactionRatio > 0)
{
RefreshVisualizerState = RefreshVisualizerState.Peeking;
}
else
{
RefreshVisualizerState = RefreshVisualizerState.Idle;
}
}
}
}
private void InteractingForRefreshObserver(bool obj)
{
_isInteractingForRefresh = obj;
if (!_isInteractingForRefresh)
{
switch (_refreshVisualizerState)
{
case RefreshVisualizerState.Pending:
RequestRefresh();
break;
case RefreshVisualizerState.Refreshing:
// We don't want to interrupt a currently executing refresh.
break;
default:
RefreshVisualizerState = RefreshVisualizerState.Idle;
break;
}
}
}
}
}

13
src/Avalonia.Controls/PullToRefresh/RefreshVisualizerOrientation.cs

@ -0,0 +1,13 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines constants that specify the orientation of a RefreshVisualizer.
/// </summary>
public enum RefreshVisualizerOrientation
{
Auto,
Normal,
Rotate90DegreesCounterclockwise,
Rotate270DegreesCounterclockwise
}
}

14
src/Avalonia.Controls/PullToRefresh/RefreshVisualizerState.cs

@ -0,0 +1,14 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines constants that specify the state of a RefreshVisualizer
/// </summary>
public enum RefreshVisualizerState
{
Idle,
Peeking,
Interacting,
Pending,
Refreshing
}
}

274
src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs

@ -0,0 +1,274 @@
using System;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Rendering.Composition;
using Avalonia.VisualTree;
namespace Avalonia.Controls.PullToRefresh
{
internal class ScrollViewerIRefreshInfoProviderAdapter
{
private const int MaxSearchDepth = 10;
private const int InitialOffsetThreshold = 1;
private PullDirection _refreshPullDirection;
private ScrollViewer? _scrollViewer;
private RefreshInfoProvider? _refreshInfoProvider;
private PullGestureRecognizer? _pullGestureRecognizer;
private InputElement? _interactionSource;
private bool _isVisualizerInteractionSourceAttached;
public ScrollViewerIRefreshInfoProviderAdapter(PullDirection pullDirection)
{
_refreshPullDirection = pullDirection;
}
public RefreshInfoProvider? AdaptFromTree(Visual root, Size? refreshVIsualizerSize)
{
if (root is ScrollViewer scrollViewer)
{
return Adapt(scrollViewer, refreshVIsualizerSize);
}
else
{
int depth = 0;
while (depth < MaxSearchDepth)
{
var scroll = AdaptFromTreeRecursiveHelper(root, depth);
if (scroll != null)
{
return Adapt(scroll, refreshVIsualizerSize);
}
depth++;
}
}
ScrollViewer? AdaptFromTreeRecursiveHelper(Visual root, int depth)
{
if (depth == 0)
{
foreach (var child in root.VisualChildren)
{
if (child is ScrollViewer viewer)
{
return viewer;
}
}
}
else
{
foreach (var child in root.VisualChildren)
{
var viewer = AdaptFromTreeRecursiveHelper(child, depth - 1);
if (viewer != null)
{
return viewer;
}
}
}
return null;
}
return null;
}
public RefreshInfoProvider Adapt(ScrollViewer adaptee, Size? refreshVIsualizerSize)
{
if (adaptee == null)
{
throw new ArgumentNullException(nameof(adaptee), "Adaptee cannot be null");
}
if (_scrollViewer != null)
{
CleanUpScrollViewer();
}
if (_refreshInfoProvider != null && _interactionSource != null)
{
_interactionSource.RemoveHandler(Gestures.PullGestureEvent, _refreshInfoProvider.InteractingStateEntered);
_interactionSource.RemoveHandler(Gestures.PullGestureEndedEvent, _refreshInfoProvider.InteractingStateExited);
}
_refreshInfoProvider = null;
_scrollViewer = adaptee;
if (_scrollViewer.Content == null)
{
throw new ArgumentException(nameof(adaptee), "Adaptee's content property cannot be null.");
}
var content = adaptee.Content as Visual;
if (content == null)
{
throw new ArgumentException(nameof(adaptee), "Adaptee's content property must be a Visual");
}
if (content.GetVisualParent() == null)
{
_scrollViewer.Loaded += ScrollViewer_Loaded;
}
else
{
ScrollViewer_Loaded(null, null);
if (content.Parent is not InputElement)
{
throw new ArgumentException(nameof(adaptee), "Adaptee's content's parent must be a InputElement");
}
}
_refreshInfoProvider = new RefreshInfoProvider(_refreshPullDirection, refreshVIsualizerSize, ElementComposition.GetElementVisual(content));
_pullGestureRecognizer = new PullGestureRecognizer(_refreshPullDirection);
if (_interactionSource != null)
{
_interactionSource.GestureRecognizers.Add(_pullGestureRecognizer);
_interactionSource.AddHandler(Gestures.PullGestureEvent, _refreshInfoProvider.InteractingStateEntered);
_interactionSource.AddHandler(Gestures.PullGestureEndedEvent, _refreshInfoProvider.InteractingStateExited);
_isVisualizerInteractionSourceAttached = true;
}
_scrollViewer.PointerPressed += ScrollViewer_PointerPressed;
_scrollViewer.PointerReleased += ScrollViewer_PointerReleased;
_scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
return _refreshInfoProvider;
}
private void ScrollViewer_ScrollChanged(object? sender, ScrollChangedEventArgs e)
{
if (_isVisualizerInteractionSourceAttached && _refreshInfoProvider != null && _refreshInfoProvider.IsInteractingForRefresh)
{
if (!IsWithinOffsetThreashold())
{
_refreshInfoProvider.IsInteractingForRefresh = false;
}
}
}
public void SetAnimations(RefreshVisualizer refreshVisualizer)
{
var visualizerComposition = ElementComposition.GetElementVisual(refreshVisualizer);
if (visualizerComposition != null)
{
var compositor = visualizerComposition.Compositor;
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
var animation = compositor.CreateImplicitAnimationCollection();
animation["Offset"] = offsetAnimation;
visualizerComposition.ImplicitAnimations = animation;
}
if(_scrollViewer != null && _scrollViewer.Content is Visual visual)
{
var scollContentComposition = ElementComposition.GetElementVisual(visual);
if(scollContentComposition != null)
{
var compositor = scollContentComposition.Compositor;
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);
var animation = compositor.CreateImplicitAnimationCollection();
animation["Offset"] = offsetAnimation;
scollContentComposition.ImplicitAnimations = animation;
}
}
}
private void ScrollViewer_Loaded(object? sender, RoutedEventArgs? e)
{
var content = _scrollViewer?.Content as Visual;
if (content == null)
{
throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual");
}
if (content.Parent is not InputElement)
{
throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement");
}
MakeInteractionSource(content.Parent as InputElement);
if (_scrollViewer != null)
{
_scrollViewer.Loaded -= ScrollViewer_Loaded;
}
}
private void MakeInteractionSource(InputElement? element)
{
_interactionSource = element;
if (_pullGestureRecognizer != null && _refreshInfoProvider != null)
{
element?.GestureRecognizers.Add(_pullGestureRecognizer);
_interactionSource?.AddHandler(Gestures.PullGestureEvent, _refreshInfoProvider.InteractingStateEntered);
_interactionSource?.AddHandler(Gestures.PullGestureEndedEvent, _refreshInfoProvider.InteractingStateExited);
_isVisualizerInteractionSourceAttached = true;
}
}
private void ScrollViewer_PointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (_refreshInfoProvider != null)
{
_refreshInfoProvider.IsInteractingForRefresh = false;
}
}
private void ScrollViewer_PointerPressed(object? sender, PointerPressedEventArgs e)
{
if (_refreshInfoProvider != null)
{
_refreshInfoProvider.PeekingMode = !IsWithinOffsetThreashold();
}
}
private bool IsWithinOffsetThreashold()
{
if (_scrollViewer != null)
{
var offset = _scrollViewer.Offset;
switch (_refreshPullDirection)
{
case PullDirection.TopToBottom:
return offset.Y < InitialOffsetThreshold;
case PullDirection.LeftToRight:
return offset.X < InitialOffsetThreshold;
case PullDirection.RightToLeft:
return offset.X > _scrollViewer.Extent.Width - _scrollViewer.Viewport.Width - InitialOffsetThreshold;
case PullDirection.BottomToTop:
return offset.Y > _scrollViewer.Extent.Height - _scrollViewer.Viewport.Height - InitialOffsetThreshold;
}
}
return false;
}
private void CleanUpScrollViewer()
{
if (_scrollViewer != null)
{
_scrollViewer.PointerPressed -= ScrollViewer_PointerPressed;
_scrollViewer.PointerReleased -= ScrollViewer_PointerReleased;
_scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
}
}
}
}

6
src/Avalonia.Controls/TopLevel.cs

@ -357,12 +357,6 @@ namespace Avalonia.Controls
/// </summary>
protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
public override void InvalidateMirrorTransform()
{
}
protected override bool BypassFlowDirectionPolicies => true;
/// <summary>
/// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>

2
src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj

@ -7,7 +7,7 @@
need change when there are breaking changes to designer support api.
-->
<Version>0.7.0</Version>
<NoWarn>CS1591</NoWarn>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NugetPackageName>Avalonia</NugetPackageName>
</PropertyGroup>

13
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using System.Threading;
@ -150,10 +151,20 @@ namespace Avalonia.DesignerSupport.Remote
}
if (args.Method == Methods.Win32)
builder.UseWindowingSubsystem("Avalonia.Win32");
builder.UseWindowingSubsystem(GetInitializer("Avalonia.Win32"), "Win32");
builder.SetupWithoutStarting();
return transport;
}
private static Action GetInitializer(string assemblyName) => () =>
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform";
var platformClassFullName = assemblyName + "." + platformClassName;
var platformClass = assembly.GetType(platformClassFullName);
var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes);
init!.Invoke(null, null);
};
}
private const string BuilderMethodName = "BuildAvaloniaApp";

4
src/Avalonia.Desktop/Avalonia.Desktop.csproj

@ -12,5 +12,7 @@
<ProjectReference Include="../Avalonia.X11/Avalonia.X11.csproj" />
</ItemGroup>
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\NullableEnable.props" />
</Project>

1
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -16,4 +16,5 @@
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
</Project>

1
src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj

@ -9,4 +9,5 @@
<PackageReference Include="Quamotion.RemoteViewing" Version="1.1.21" />
</ItemGroup>
<Import Project="..\..\build\TrimmingEnable.props" />
</Project>

6
src/Avalonia.Headless/Avalonia.Headless.csproj

@ -5,5 +5,9 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
</ItemGroup>
</ItemGroup>
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
</Project>

2
src/Avalonia.MicroCom/Avalonia.MicroCom.csproj

@ -13,4 +13,6 @@
</ProjectReference>
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\NullableEnable.props" />
</Project>

2
src/Avalonia.MicroCom/CallbackBase.cs

@ -38,7 +38,7 @@ namespace Avalonia.MicroCom
}
}
public MicroComShadow Shadow { get; set; }
public MicroComShadow? Shadow { get; set; }
public void OnReferencedFromNative()
{
lock (_lock)

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

Loading…
Cancel
Save