diff --git a/.editorconfig b/.editorconfig index 9ae52b8bbd..238e9887bd 100644 --- a/.editorconfig +++ b/.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,14 +137,30 @@ 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 dotnet_diagnostic.CA1802.severity = warning # CA1820: Test for empty strings using string length dotnet_diagnostic.CA1820.severity = warning # CA1821: Remove empty finalizers dotnet_diagnostic.CA1821.severity = warning +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = suggestion +dotnet_code_quality.CA1822.api_surface = private, internal # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning +# CA1826: Use property instead of Linq Enumerable method +dotnet_diagnostic.CA1826.severity = suggestion +# CA1827: Do not use Count/LongCount when Any can be used +dotnet_diagnostic.CA1827.severity = warning +# CA1828: Do not use CountAsync/LongCountAsync when AnyAsync can be used +dotnet_diagnostic.CA1828.severity = warning +# CA1829: Use Length/Count property instead of Enumerable.Count method +dotnet_diagnostic.CA1829.severity = warning #CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters dotnet_diagnostic.CA1847.severity = warning diff --git a/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject b/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject similarity index 100% rename from .ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject rename to .ncrunch/Avalonia.Benchmarks.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Web.v3.ncrunchproject b/.ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject similarity index 100% rename from .ncrunch/Avalonia.Web.v3.ncrunchproject rename to .ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject diff --git a/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject b/.ncrunch/Avalonia.Browser.v3.ncrunchproject similarity index 100% rename from .ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject rename to .ncrunch/Avalonia.Browser.v3.ncrunchproject diff --git a/.ncrunch/ControlCatalog.Web.v3.ncrunchproject b/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject similarity index 100% rename from .ncrunch/ControlCatalog.Web.v3.ncrunchproject rename to .ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject b/.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject b/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/MobileSandbox.v3.ncrunchproject b/.ncrunch/MobileSandbox.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/MobileSandbox.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/ReactiveUIDemo.v3.ncrunchproject b/.ncrunch/ReactiveUIDemo.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/ReactiveUIDemo.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 6ba05332be..3fa8e969c8 100644 --- a/Avalonia.Desktop.slnf +++ b/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", diff --git a/Avalonia.sln b/Avalonia.sln index 34b5596119..7efb294b64 100644 --- a/Avalonia.sln +++ b/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} diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 5838519596..e9c3d65b41 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -7,7 +7,7 @@ https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ true - CS1591 + $(NoWarn);CS1591 preview MIT Icon.png diff --git a/build/TrimmingEnable.props b/build/TrimmingEnable.props new file mode 100644 index 0000000000..3f873e3345 --- /dev/null +++ b/build/TrimmingEnable.props @@ -0,0 +1,16 @@ + + + false + true + false + true + + + + true + + $(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);IL3050;IL3051;IL3052;IL3053;IL3054;IL3055;IL3056 + + diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 2443965957..b6dacb6ce4 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/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() { diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 2295c0beda..3704cee890 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -80,7 +80,6 @@ partial class Build : NukeBuild if (Parameters.IsRunningOnAzure) c.AddProperty("JavaSdkDirectory", GetVariable("JAVA_HOME_11_X64")); c.AddProperty("PackageVersion", Parameters.Version) - .AddProperty("iOSRoslynPathHackRequired", true) .SetConfiguration(Parameters.Configuration) .SetVerbosity(DotNetVerbosity.Minimal); return c; diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 92d9732e91..13bac4b7db 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -4,7 +4,7 @@ false False - CS0649;CS0169;SYSLIB0011 + $(NoWarn);CS0649;CS0169;SYSLIB0011 1 net7.0 diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 4f9c4d7720..ca2d4b66ed 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -30,7 +30,9 @@ /> - + $(IntermediateOutputPath)/Avalonia/resources @@ -62,7 +64,9 @@ BeforeTargets="CoreCompile;CoreResGen" Inputs="@(AvaloniaResource);@(AvaloniaXaml);@(CustomAdditionalGenerateAvaloniaResourcesInputs);$(MSBuildAllProjects)" Outputs="$(AvaloniaResourcesTemporaryFilePath)" - DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)"> + DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)" + Condition="('@(AvaloniaResource->Count())' > 0) or ('@(AvaloniaXaml->Count())' > 0)" + > @@ -74,14 +78,19 @@ ReportImportance="$(AvaloniaXamlReportImportance)"/> + Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/> $(IntermediateOutputPath)/Avalonia/references @@ -112,7 +121,7 @@ /> + Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/> diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml index 175e838616..5a8e65ed22 100644 --- a/samples/BindingDemo/App.xaml +++ b/samples/BindingDemo/App.xaml @@ -2,8 +2,14 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="BindingDemo.App"> - - - - + + + + + + + + + + diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index b1bacc6483..d5e5cb14dc 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -99,6 +99,15 @@ namespace ControlCatalog.NetCore SilenceConsole(); return builder.StartLinuxDrm(args, scaling: GetScaling()); } + else if (args.Contains("--dxgi")) + { + builder.With(new Win32PlatformOptions() + { + UseLowLatencyDxgiSwapChain = true, + UseWindowsUIComposition = false + }); + return builder.StartWithClassicDesktopLifetime(args); + } else return builder.StartWithClassicDesktopLifetime(args); } diff --git a/samples/ControlCatalog.NetCore/Properties/launchSettings.json b/samples/ControlCatalog.NetCore/Properties/launchSettings.json new file mode 100644 index 0000000000..5964ca320e --- /dev/null +++ b/samples/ControlCatalog.NetCore/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "ControlCatalog.NetCore": { + "commandName": "Project" + }, + "Dxgi": { + "commandName": "Project", + "commandLineArgs": "--dxgi" + } + } +} \ No newline at end of file diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 6b550a30be..18f0dd16ba 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -33,4 +33,14 @@ + + + + + + + + + + diff --git a/samples/ControlCatalog/Converter/DegToRadConverter.cs b/samples/ControlCatalog/Converter/DegToRadConverter.cs new file mode 100644 index 0000000000..b062bcb64a --- /dev/null +++ b/samples/ControlCatalog/Converter/DegToRadConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using Avalonia.Data.Converters; + +namespace ControlCatalog.Converter +{ + public class DegToRadConverter : IValueConverter + { + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is double rad) + { + return rad * 180.0d / Math.PI; + } + return 0.0d; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is double deg) + { + return deg / 180.0d * Math.PI; + } + return 0.0d; + } + } +} diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 33ea8c0db0..4a5f5bc96c 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -66,6 +66,9 @@ + + + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 8d9cd74ea7..ae7e43f511 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -14,7 +14,26 @@ namespace ControlCatalog.Pages { public class AutoCompleteBoxPage : UserControl { - private StateData[] BuildAllStates() + public class StateData + { + public string Name { get; private set; } + public string Abbreviation { get; private set; } + public string Capital { get; private set; } + + public StateData(string name, string abbreviatoin, string capital) + { + Name = name; + Abbreviation = abbreviatoin; + Capital = capital; + } + + public override string ToString() + { + return Name; + } + } + + private static StateData[] BuildAllStates() { return new StateData[] { @@ -72,7 +91,7 @@ namespace ControlCatalog.Pages } public StateData[] States { get; private set; } - private LinkedList[] BuildAllSentences() + private static LinkedList[] BuildAllSentences() { return new string[] { @@ -124,7 +143,7 @@ namespace ControlCatalog.Pages .OfType(); } - private bool StringContains(string str, string? query) + private static bool StringContains(string str, string? query) { if (query == null) return false; return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index 61e0ed5acb..c70675b606 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -25,7 +25,7 @@ public partial class CompositionPage : UserControl this.Get("Items").Items = CreateColorItems(); } - private List CreateColorItems() + private static List CreateColorItems() { var list = new List(); diff --git a/samples/ControlCatalog/Pages/CustomDrawing.xaml b/samples/ControlCatalog/Pages/CustomDrawing.xaml new file mode 100644 index 0000000000..04b7fcfea5 --- /dev/null +++ b/samples/ControlCatalog/Pages/CustomDrawing.xaml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index c1acc7ca88..841947673a 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/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") diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index c3a0d8d2e2..00987429d0 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -27,10 +27,10 @@ - Normal - Minimized - Maximized - FullScreen + Normal + Minimized + Maximized + FullScreen diff --git a/samples/ReactiveUIDemo/App.axaml b/samples/ReactiveUIDemo/App.axaml new file mode 100644 index 0000000000..dd3a39f6ac --- /dev/null +++ b/samples/ReactiveUIDemo/App.axaml @@ -0,0 +1,8 @@ + + + + + diff --git a/samples/ReactiveUIDemo/App.axaml.cs b/samples/ReactiveUIDemo/App.axaml.cs new file mode 100644 index 0000000000..4578566427 --- /dev/null +++ b/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)); + Locator.CurrentMutable.Register(() => new BarView(), typeof(IViewFor)); + } + + 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() + .UsePlatformDetect() + .UseReactiveUI() + .LogToTrace(); + } +} diff --git a/samples/ReactiveUIDemo/MainWindow.axaml b/samples/ReactiveUIDemo/MainWindow.axaml new file mode 100644 index 0000000000..7775fc5a79 --- /dev/null +++ b/samples/ReactiveUIDemo/MainWindow.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/samples/ReactiveUIDemo/MainWindow.axaml.cs b/samples/ReactiveUIDemo/MainWindow.axaml.cs new file mode 100644 index 0000000000..5bf2d476fd --- /dev/null +++ b/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); + } + } +} diff --git a/samples/ReactiveUIDemo/ReactiveUIDemo.csproj b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj new file mode 100644 index 0000000000..94ca4ee809 --- /dev/null +++ b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj @@ -0,0 +1,28 @@ + + + Exe + net6.0 + enable + + + + + + + + + + + BarView.axaml + + + FooView.axaml + + + + + + + + + diff --git a/samples/ReactiveUIDemo/ViewModels/BarViewModel.cs b/samples/ReactiveUIDemo/ViewModels/BarViewModel.cs new file mode 100644 index 0000000000..3448453d81 --- /dev/null +++ b/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; } + } +} diff --git a/samples/ReactiveUIDemo/ViewModels/FooViewModel.cs b/samples/ReactiveUIDemo/ViewModels/FooViewModel.cs new file mode 100644 index 0000000000..1a363e18dc --- /dev/null +++ b/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; } + } +} diff --git a/samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs b/samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000000..2222137d38 --- /dev/null +++ b/samples/ReactiveUIDemo/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,9 @@ +using ReactiveUI; + +namespace ReactiveUIDemo.ViewModels +{ + internal class MainWindowViewModel : ReactiveObject + { + public RoutedViewHostPageViewModel RoutedViewHost { get; } = new(); + } +} diff --git a/samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs b/samples/ReactiveUIDemo/ViewModels/RoutedViewHostPageViewModel.cs new file mode 100644 index 0000000000..701447cfe8 --- /dev/null +++ b/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); + } +} diff --git a/samples/ReactiveUIDemo/Views/BarView.axaml b/samples/ReactiveUIDemo/Views/BarView.axaml new file mode 100644 index 0000000000..2622245997 --- /dev/null +++ b/samples/ReactiveUIDemo/Views/BarView.axaml @@ -0,0 +1,16 @@ + + + + Bar! + + + diff --git a/samples/ReactiveUIDemo/Views/BarView.axaml.cs b/samples/ReactiveUIDemo/Views/BarView.axaml.cs new file mode 100644 index 0000000000..2fbea6de91 --- /dev/null +++ b/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 + { + public BarView() + { + InitializeComponent(); + } + + public BarViewModel? ViewModel { get; set; } + + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (BarViewModel?)value; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ReactiveUIDemo/Views/FooView.axaml b/samples/ReactiveUIDemo/Views/FooView.axaml new file mode 100644 index 0000000000..8f73250d3b --- /dev/null +++ b/samples/ReactiveUIDemo/Views/FooView.axaml @@ -0,0 +1,16 @@ + + + + Foo! + + + diff --git a/samples/ReactiveUIDemo/Views/FooView.axaml.cs b/samples/ReactiveUIDemo/Views/FooView.axaml.cs new file mode 100644 index 0000000000..313a71044c --- /dev/null +++ b/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 + { + public FooView() + { + InitializeComponent(); + } + + public FooViewModel? ViewModel { get; set; } + + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (FooViewModel?)value; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index b6adbde738..8d56086470 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -95,6 +95,10 @@ namespace Avalonia.Android _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset); } + else + { + _imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.ImplicitOnly); + } } private void SurroundingTextChanged(object sender, EventArgs e) diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 6688dde8f5..66557418dd 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -16,4 +16,5 @@ + diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index de8d02f188..b24581fb8b 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -4,9 +4,12 @@ using Android.Content; using Android.Content.Res; using Android.OS; using Android.Runtime; +using Android.Views; using AndroidX.AppCompat.App; using AndroidX.Lifecycle; +using AndroidRect = Android.Graphics.Rect; + namespace Avalonia.Android { public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler @@ -15,6 +18,7 @@ namespace Avalonia.Android public Action ActivityResult { get; set; } internal AvaloniaView View; + private GlobalLayoutListener _listener; protected override void OnCreate(Bundle savedInstanceState) { @@ -32,6 +36,10 @@ namespace Avalonia.Android base.OnCreate(savedInstanceState); SetContentView(View); + + _listener = new GlobalLayoutListener(View); + + View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener); } public object Content @@ -57,6 +65,8 @@ namespace Avalonia.Android { View.Content = null; + View.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener); + base.OnDestroy(); } @@ -66,5 +76,20 @@ namespace Avalonia.Android ActivityResult?.Invoke(requestCode, resultCode, data); } + + class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener + { + private AvaloniaView _view; + + public GlobalLayoutListener(AvaloniaView view) + { + _view = view; + } + + public void OnGlobalLayout() + { + _view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize); + } + } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 5f53eb36c8..4150b52946 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -26,6 +26,7 @@ using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; +using AndroidRect = Android.Graphics.Rect; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -63,7 +64,21 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IInputRoot InputRoot { get; private set; } - public virtual Size ClientSize => Size.ToSize(RenderScaling); + public virtual Size ClientSize + { + get + { + AndroidRect rect = new AndroidRect(); + AndroidRect intersection = new AndroidRect(); + + _view.GetWindowVisibleDisplayFrame(intersection); + _view.GetGlobalVisibleRect(rect); + + intersection.Intersect(rect); + + return new PixelSize(intersection.Right - intersection.Left, intersection.Bottom - intersection.Top).ToSize(RenderScaling); + } + } public Size? FrameSize => null; @@ -94,7 +109,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform ? new CompositingRenderer(root, AndroidPlatform.Compositor) : AndroidPlatform.Options.UseDeferredRendering ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true } - : new ImmediateRenderer(root); + : new ImmediateRenderer((Visual)root); public virtual void Hide() { @@ -149,6 +164,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform Resized?.Invoke(size, PlatformResizeReason.Unspecified); } + internal void Resize(Size size) + { + Resized?.Invoke(size, PlatformResizeReason.Layout); + } + class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo { private readonly TopLevelImpl _tl; diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 57f6469a42..bc24a16e56 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -30,7 +30,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers return DispatchKeyEventInternal(e, out callBase); } - string UnicodeTextInput(KeyEvent keyEvent) + static string UnicodeTextInput(KeyEvent keyEvent) { return keyEvent.Action == KeyEventActions.Multiple && keyEvent.RepeatCount == 0 diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index a9b2e16d43..078f70db60 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -178,7 +178,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF return false; } - private Stream? GetVirtualFileStream(Context context, AndroidUri uri, bool isOutput) + private static Stream? GetVirtualFileStream(Context context, AndroidUri uri, bool isOutput) { var mimeTypes = context.ContentResolver?.GetStreamTypes(uri, FilePickerFileTypes.All.MimeTypes![0]); if (mimeTypes?.Length >= 1) diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index e0883901fd..06087cdd6a 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/src/Avalonia.Base/Animation/Animation.cs @@ -202,9 +202,7 @@ namespace Avalonia.Animation /// The animation setter. /// The property animator value. 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)!); diff --git a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs index 0356723f00..3168a67d79 100644 --- a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs +++ b/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() + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] + public T GetTypedValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() { var typeConv = TypeDescriptor.GetConverter(typeof(T)); diff --git a/src/Avalonia.Base/Animation/Animators/Animator`1.cs b/src/Avalonia.Base/Animation/Animators/Animator`1.cs index 8765cfb4c9..b5d1feb4a7 100644 --- a/src/Avalonia.Base/Animation/Animators/Animator`1.cs +++ b/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(this, animation, control, clock, onComplete); - return match.Subscribe(subject); + return new CompositeDisposable(match.Subscribe(subject), subject); } protected T InterpolationHandler(double animationTime, T neutralValue) diff --git a/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs index 4727ea1bfb..068c190fa1 100644 --- a/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs @@ -72,7 +72,7 @@ namespace Avalonia.Animation.Animators return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } - private IReadOnlyList InterpolateStops(double progress, IReadOnlyList oldValue, IReadOnlyList newValue) + private static IReadOnlyList InterpolateStops(double progress, IReadOnlyList oldValue, IReadOnlyList newValue) { var resultCount = Math.Max(oldValue.Count, newValue.Count); var stops = new ImmutableGradientStop[resultCount]; diff --git a/src/Avalonia.Base/Animation/CrossFade.cs b/src/Avalonia.Base/Animation/CrossFade.cs index 608a0880ec..a229bc7ce6 100644 --- a/src/Avalonia.Base/Animation/CrossFade.cs +++ b/src/Avalonia.Base/Animation/CrossFade.cs @@ -10,7 +10,7 @@ using Avalonia.VisualTree; namespace Avalonia.Animation { /// - /// Defines a cross-fade animation between two s. + /// Defines a cross-fade animation between two s. /// public class CrossFade : IPageTransition { diff --git a/src/Avalonia.Base/Animation/KeySpline.cs b/src/Avalonia.Base/Animation/KeySpline.cs index a6e9769186..6ca5b2e759 100644 --- a/src/Avalonia.Base/Animation/KeySpline.cs +++ b/src/Avalonia.Base/Animation/KeySpline.cs @@ -196,7 +196,7 @@ namespace Avalonia.Animation /// /// /// - private bool IsValidXValue(double value) + private static bool IsValidXValue(double value) { return value >= 0.0 && value <= 1.0; } diff --git a/src/Avalonia.Base/Animation/PageSlide.cs b/src/Avalonia.Base/Animation/PageSlide.cs index b22bc8b243..609441ae8c 100644 --- a/src/Avalonia.Base/Animation/PageSlide.cs +++ b/src/Avalonia.Base/Animation/PageSlide.cs @@ -157,7 +157,7 @@ namespace Avalonia.Animation /// /// Any one of the parameters may be null, but not both. /// - protected static IVisual GetVisualParent(IVisual? from, IVisual? to) + protected static Visual GetVisualParent(Visual? from, Visual? to) { var p1 = (from ?? to)!.VisualParent; var p2 = (to ?? from)!.VisualParent; diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 12c569c452..a43194153c 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -34,7 +34,7 @@ namespace Avalonia /// /// The owner type. /// The property. - public new AttachedProperty AddOwner() where TOwner : IAvaloniaObject + public new AttachedProperty AddOwner() where TOwner : AvaloniaObject { AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); return this; diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 21bdb794b3..0d3da66f7a 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -19,6 +19,7 @@ + @@ -30,6 +31,8 @@ + + @@ -41,6 +44,7 @@ + @@ -48,14 +52,10 @@ - + - diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 68c8f19f19..a3a732428e 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -16,7 +16,7 @@ namespace Avalonia /// /// This class is analogous to DependencyObject in WPF. /// - public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged + public class AvaloniaObject : IAvaloniaObjectDebug, INotifyPropertyChanged { private readonly ValueStore _values; private AvaloniaObject? _inheritanceParent; diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 9644063da7..867d6215a5 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -36,7 +36,7 @@ namespace Avalonia /// /// The subscription to is created using a weak reference. /// - public static IObservable GetObservable(this IAvaloniaObject o, AvaloniaProperty property) + public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyObservable( o ?? throw new ArgumentNullException(nameof(o)), @@ -56,7 +56,7 @@ namespace Avalonia /// /// The subscription to is created using a weak reference. /// - public static IObservable GetObservable(this IAvaloniaObject o, AvaloniaProperty property) + public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyObservable( o ?? throw new ArgumentNullException(nameof(o)), @@ -76,7 +76,7 @@ namespace Avalonia /// The subscription to is created using a weak reference. /// public static IObservable> GetBindingObservable( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyBindingObservable( @@ -98,7 +98,7 @@ namespace Avalonia /// The subscription to is created using a weak reference. /// public static IObservable> GetBindingObservable( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyBindingObservable( @@ -115,11 +115,11 @@ namespace Avalonia /// The property. /// /// An observable which when subscribed pushes the property changed event args - /// each time a event is raised + /// each time a event is raised /// for the specified property. /// public static IObservable GetPropertyChangedObservable( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyChangedObservable( @@ -140,7 +140,7 @@ namespace Avalonia /// property. /// public static ISubject GetSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -163,7 +163,7 @@ namespace Avalonia /// property. /// public static ISubject GetSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -185,7 +185,7 @@ namespace Avalonia /// property. /// public static ISubject> GetBindingSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -214,7 +214,7 @@ namespace Avalonia /// property. /// public static ISubject> GetBindingSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -241,7 +241,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public static IDisposable Bind( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property, IObservable> source, BindingPriority priority = BindingPriority.LocalValue) @@ -250,17 +250,12 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); source = source ?? throw new ArgumentNullException(nameof(source)); - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.Bind(styled, source, priority), - DirectPropertyBase direct => ao.Bind(direct, source), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), - }; - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.Bind(styled, source, priority), + DirectPropertyBase direct => target.Bind(direct, source), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), + }; } /// @@ -274,22 +269,17 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public static IDisposable Bind( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.Bind(styled, source, priority), - DirectPropertyBase direct => ao.Bind(direct, source), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), - }; - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.Bind(styled, source, priority), + DirectPropertyBase direct => target.Bind(direct, source), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), + }; } /// @@ -306,7 +296,7 @@ namespace Avalonia /// /// An which can be used to cancel the binding. public static IDisposable Bind( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property, IBinding binding, object? anchor = null) @@ -340,23 +330,17 @@ namespace Avalonia /// The object. /// The property. /// The value. - public static T GetValue(this IAvaloniaObject target, AvaloniaProperty property) + public static T GetValue(this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.GetValue(styled), - DirectPropertyBase direct => ao.GetValue(direct), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") - }; - - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.GetValue(styled), + DirectPropertyBase direct => target.GetValue(direct), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") + }; } /// @@ -372,15 +356,13 @@ namespace Avalonia /// For direct properties returns the current value of the property. /// public static object? GetBaseValue( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); - if (target is AvaloniaObject ao) - return property.RouteGetBaseValue(ao); - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + return property.RouteGetBaseValue(target); } /// @@ -396,24 +378,18 @@ namespace Avalonia /// For direct properties returns the current value of the property. /// public static Optional GetBaseValue( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.GetBaseValue(styled), - DirectPropertyBase direct => ao.GetValue(direct), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") - }; - - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.GetBaseValue(styled), + DirectPropertyBase direct => target.GetValue(direct), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") + }; } /// @@ -474,7 +450,7 @@ namespace Avalonia } public InstancedBinding? Initiate( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 59a61e3424..e0782c51a2 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/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; @@ -38,7 +39,7 @@ namespace Avalonia Type valueType, Type ownerType, AvaloniaPropertyMetadata metadata, - Action? notifying = null) + Action? notifying = null) { _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -145,7 +146,7 @@ namespace Avalonia /// will be true before the property change notifications are sent and false afterwards. This /// callback is intended to support Control.IsDataContextChanging. /// - public Action? Notifying { get; } + public Action? Notifying { get; } /// /// Gets the integer ID that represents this property. @@ -177,7 +178,7 @@ namespace Avalonia { return new IndexerDescriptor { - Priority = BindingPriority.TemplatedParent, + Priority = BindingPriority.Template, Property = property, }; } @@ -238,9 +239,9 @@ namespace Avalonia bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func? validate = null, - Func? coerce = null, - Action? notifying = null) - where TOwner : IAvaloniaObject + Func? coerce = null, + Action? notifying = null) + where TOwner : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -279,8 +280,8 @@ namespace Avalonia bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func? validate = null, - Func? coerce = null) - where THost : IAvaloniaObject + Func? coerce = null) + where THost : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -316,8 +317,8 @@ namespace Avalonia bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func? validate = null, - Func? coerce = null) - where THost : IAvaloniaObject + Func? coerce = null) + where THost : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -354,7 +355,7 @@ namespace Avalonia TValue unsetValue = default!, BindingMode defaultBindingMode = BindingMode.OneWay, bool enableDataValidation = false) - where TOwner : IAvaloniaObject + where TOwner : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); _ = getter ?? throw new ArgumentNullException(nameof(getter)); @@ -415,7 +416,7 @@ namespace Avalonia /// /// The property metadata. /// - public AvaloniaPropertyMetadata GetMetadata() where T : IAvaloniaObject + public AvaloniaPropertyMetadata GetMetadata() where T : AvaloniaObject { return GetMetadata(typeof(T)); } @@ -442,6 +443,7 @@ namespace Avalonia /// /// The value. /// True if the value is valid, otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public bool IsValidValue(object? value) { return TypeUtilities.TryConvertImplicit(PropertyType, value, out _); diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs index a3ca25bc45..997a363c26 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs @@ -9,7 +9,7 @@ namespace Avalonia public abstract class AvaloniaPropertyChangedEventArgs : EventArgs { public AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, BindingPriority priority) { Sender = sender; @@ -18,7 +18,7 @@ namespace Avalonia } internal AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, BindingPriority priority, bool isEffectiveValueChange) { @@ -31,7 +31,7 @@ namespace Avalonia /// Gets the that the property changed on. /// /// The sender object. - public IAvaloniaObject Sender { get; } + public AvaloniaObject Sender { get; } /// /// Gets the property that changed. diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs index 2c7a597537..70c75d24e7 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs @@ -16,7 +16,7 @@ namespace Avalonia /// The new value of the property. /// The priority of the binding that produced the value. public AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, AvaloniaProperty property, Optional oldValue, BindingValue newValue, @@ -26,7 +26,7 @@ namespace Avalonia } internal AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, AvaloniaProperty property, Optional oldValue, BindingValue newValue, diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 62265d3c59..fc0ca2323e 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/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 /// /// The type. /// A collection of definitions. + [UnconditionalSuppressMessage("Trimming", "IL2059", Justification = "If type was trimmed out, no properties were referenced")] public IReadOnlyList GetRegistered(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); @@ -189,7 +191,7 @@ namespace Avalonia /// /// The object. /// A collection of definitions. - public IReadOnlyList GetRegistered(IAvaloniaObject o) + public IReadOnlyList GetRegistered(AvaloniaObject o) { _ = o ?? throw new ArgumentNullException(nameof(o)); @@ -205,7 +207,7 @@ namespace Avalonia /// The registered. /// public DirectPropertyBase GetRegisteredDirect( - IAvaloniaObject o, + AvaloniaObject o, DirectPropertyBase property) { return FindRegisteredDirect(o, property) ?? @@ -260,7 +262,7 @@ namespace Avalonia /// /// The property name contains a '.'. /// - public AvaloniaProperty? FindRegistered(IAvaloniaObject o, string name) + public AvaloniaProperty? FindRegistered(AvaloniaObject o, string name) { _ = o ?? throw new ArgumentNullException(nameof(o)); _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -277,7 +279,7 @@ namespace Avalonia /// The registered property or null if no matching property found. /// public DirectPropertyBase? FindRegisteredDirect( - IAvaloniaObject o, + AvaloniaObject o, DirectPropertyBase property) { if (property.Owner == o.GetType()) @@ -362,7 +364,7 @@ namespace Avalonia /// The property. /// /// You won't usually want to call this method directly, instead use the - /// + /// /// method. /// public void Register(Type type, AvaloniaProperty property) @@ -413,7 +415,7 @@ namespace Avalonia /// The property. /// /// You won't usually want to call this method directly, instead use the - /// + /// /// method. /// public void RegisterAttached(Type type, AvaloniaProperty property) diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 14acc2c47f..53444ee475 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/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; @@ -24,7 +25,7 @@ namespace Avalonia string name, Type ownerType, AvaloniaPropertyMetadata metadata, - Action? notifying = null) + Action? notifying = null) : base(name, typeof(TValue), ownerType, metadata, notifying) { _changed = new Subject>(); @@ -67,6 +68,7 @@ namespace Avalonia protected override IObservable GetChanged() => Changed; + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] protected BindingValue TryConvert(object? value) { if (value == UnsetValue) diff --git a/src/Avalonia.Base/ClassBindingManager.cs b/src/Avalonia.Base/ClassBindingManager.cs index e8b1cc301d..589b9b2d01 100644 --- a/src/Avalonia.Base/ClassBindingManager.cs +++ b/src/Avalonia.Base/ClassBindingManager.cs @@ -9,7 +9,7 @@ namespace Avalonia private static readonly Dictionary s_RegisteredProperties = new Dictionary(); - public static IDisposable Bind(IStyledElement target, string className, IBinding source, object anchor) + public static IDisposable Bind(StyledElement target, string className, IBinding source, object anchor) { if (!s_RegisteredProperties.TryGetValue(className, out var prop)) s_RegisteredProperties[className] = prop = RegisterClassProxyProperty(className); @@ -21,7 +21,7 @@ namespace Avalonia var prop = AvaloniaProperty.Register("__AvaloniaReserved::Classes::" + className); prop.Changed.Subscribe(args => { - var classes = ((IStyledElement)args.Sender).Classes; + var classes = ((StyledElement)args.Sender).Classes; classes.Set(className, args.NewValue.GetValueOrDefault()); }); diff --git a/src/Avalonia.Base/Collections/AvaloniaListConverter.cs b/src/Avalonia.Base/Collections/AvaloniaListConverter.cs index b3fc0b01b6..34ccb5e65f 100644 --- a/src/Avalonia.Base/Collections/AvaloniaListConverter.cs +++ b/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 /// /// Creates an from a string representation. /// - public class AvaloniaListConverter : TypeConverter + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] + public class AvaloniaListConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { diff --git a/src/Avalonia.Base/Compatibility/TrimmingAttributes.cs b/src/Avalonia.Base/Compatibility/TrimmingAttributes.cs new file mode 100644 index 0000000000..941faa46bc --- /dev/null +++ b/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 +} + diff --git a/src/Avalonia.Base/Controls/Classes.cs b/src/Avalonia.Base/Controls/Classes.cs index c3d3fbca46..e41e1cc488 100644 --- a/src/Avalonia.Base/Controls/Classes.cs +++ b/src/Avalonia.Base/Controls/Classes.cs @@ -6,7 +6,7 @@ using Avalonia.Utilities; namespace Avalonia.Controls { /// - /// Holds a collection of style classes for an . + /// Holds a collection of style classes for an . /// /// /// Similar to CSS, each control may have any number of styling classes applied. @@ -37,7 +37,7 @@ namespace Avalonia.Controls /// The initial items. public Classes(params string[] items) : base(items) - { + { } /// @@ -320,7 +320,7 @@ namespace Avalonia.Controls listener.Changed(); } - private void ThrowIfPseudoclass(string name, string operation) + private static void ThrowIfPseudoclass(string name, string operation) { if (name.StartsWith(":")) { diff --git a/src/Avalonia.Base/Controls/ISetInheritanceParent.cs b/src/Avalonia.Base/Controls/ISetInheritanceParent.cs index e85e025005..9c83c96e3f 100644 --- a/src/Avalonia.Base/Controls/ISetInheritanceParent.cs +++ b/src/Avalonia.Base/Controls/ISetInheritanceParent.cs @@ -17,6 +17,6 @@ namespace Avalonia.Controls /// Sets the control's inheritance parent. /// /// The parent. - void SetParent(IAvaloniaObject? parent); + void SetParent(AvaloniaObject? parent); } } diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 6121646107..0bf1073098 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -2,6 +2,7 @@ using Avalonia.Data.Converters; using Avalonia.LogicalTree; using Avalonia.Reactive; +using Avalonia.Styling; #nullable enable @@ -49,7 +50,7 @@ namespace Avalonia.Controls return true; } - current = (current as IStyledElement)?.StylingParent as IResourceNode; + current = (current as IStyleHost)?.StylingParent as IResourceNode; } value = null; diff --git a/src/Avalonia.Base/CornerRadius.cs b/src/Avalonia.Base/CornerRadius.cs index 893f7c4565..d56a0ef19d 100644 --- a/src/Avalonia.Base/CornerRadius.cs +++ b/src/Avalonia.Base/CornerRadius.cs @@ -100,7 +100,7 @@ namespace Avalonia public override string ToString() { - return $"{TopLeft},{TopRight},{BottomRight},{BottomLeft}"; + return FormattableString.Invariant($"{TopLeft},{TopRight},{BottomRight},{BottomLeft}"); } public static CornerRadius Parse(string s) diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs index 4e47a720f0..ceb3f71285 100644 --- a/src/Avalonia.Base/Data/BindingOperations.cs +++ b/src/Avalonia.Base/Data/BindingOperations.cs @@ -9,7 +9,7 @@ namespace Avalonia.Data public static readonly object DoNothing = new DoNothingType(); /// - /// Applies an a property on an . + /// Applies an a property on an . /// /// The target object. /// The property to bind. @@ -22,7 +22,7 @@ namespace Avalonia.Data /// /// An which can be used to cancel the binding. public static IDisposable Apply( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property, InstancedBinding binding, object? anchor) diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs index dd1654f53c..5fd5aae43b 100644 --- a/src/Avalonia.Base/Data/BindingPriority.cs +++ b/src/Avalonia.Base/Data/BindingPriority.cs @@ -1,7 +1,9 @@ +using System; + namespace Avalonia.Data { /// - /// The priority of a binding. + /// The priority of a value or binding. /// public enum BindingPriority { @@ -16,23 +18,22 @@ namespace Avalonia.Data LocalValue = 0, /// - /// A triggered style binding. + /// A triggered style value. /// /// /// A style trigger is a selector such as .class which overrides a - /// binding. In this way, a basic control can have - /// for example a Background from the templated parent which changes when the - /// control has the :pointerover class. + /// value. In this way, a control can have, e.g. a Background from + /// the template which changes when the control has the :pointerover class. /// StyleTrigger, /// - /// A binding to a property on the templated parent. + /// A value from the control's template. /// - TemplatedParent, + Template, /// - /// A style binding. + /// A style value. /// Style, @@ -42,8 +43,11 @@ namespace Avalonia.Data Inherited, /// - /// The binding is uninitialized. + /// The value is uninitialized. /// Unset = int.MaxValue, + + [Obsolete("Use Template priority")] + TemplatedParent = Template, } } diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 4bb3ad08d5..4e07ebf445 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/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 /// /// The untyped value. /// The typed binding value. + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static BindingValue FromUntyped(object? value) { return FromUntyped(value, typeof(T)); @@ -249,6 +251,7 @@ namespace Avalonia.Data /// The untyped value. /// The runtime target type. /// The typed binding value. + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static BindingValue FromUntyped(object? value, Type targetType) { if (value == AvaloniaProperty.UnsetValue) diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index c4f4362537..f5c135459d 100644 --- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs +++ b/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. /// + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] public class DefaultValueConverter : IValueConverter { /// diff --git a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs index 0672185a55..b42debc358 100644 --- a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs +++ b/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 AlwaysEnabled = (_) => true; diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs index 92ecdafa0c..e340966983 100644 --- a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs @@ -24,7 +24,7 @@ namespace Avalonia.Data.Core { try { - if (Target.TryGetTarget(out var target) && target is IAvaloniaObject obj) + if (Target.TryGetTarget(out var target) && target is AvaloniaObject obj) { obj.SetValue(_property, value, priority); return true; @@ -39,7 +39,7 @@ namespace Avalonia.Data.Core protected override void StartListeningCore(WeakReference reference) { - if (reference.TryGetTarget(out var target) && target is IAvaloniaObject obj) + if (reference.TryGetTarget(out var target) && target is AvaloniaObject obj) { _subscription = new AvaloniaPropertyObservable(obj, _property).Subscribe(ValueChanged); } diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index ea903c1042..f60b4722d9 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/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. /// + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] public class BindingExpression : LightweightObservableBase, ISubject, IDescription { private readonly ExpressionObserver _inner; diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index 4f755ff140..e4b833176c 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -159,7 +159,7 @@ namespace Avalonia.Data.Core _listening = false; } - private BindingNotification TargetNullNotification() + private static BindingNotification TargetNullNotification() { return new BindingNotification( new MarkupBindingChainException("Null value"), diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 0c7f576da6..0a9f834aeb 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/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 /// /// A description of the expression. If null, 's string representation will be used. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( T? root, Expression> expression, @@ -144,6 +146,7 @@ namespace Avalonia.Data.Core /// /// A description of the expression. If null, 's string representation will be used. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( IObservable rootObservable, Expression> expression, @@ -168,6 +171,7 @@ namespace Avalonia.Data.Core /// /// A description of the expression. If null, 's string representation will be used. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ExpressionSafeSupressWarningMessage)] public static ExpressionObserver Create( Func rootGetter, Expression> 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); diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs index d2035a592f..c48be6a175 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs +++ b/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); diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs index 1e82214d76..e1e5a705f0 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs +++ b/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"; @@ -81,7 +83,7 @@ namespace Avalonia.Data.Core.Parsers return node; } - private T GetArgumentExpressionValue(Expression expr) + private static T GetArgumentExpressionValue(Expression expr) { try { @@ -193,7 +195,7 @@ namespace Avalonia.Data.Core.Parsers throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType!.AssemblyQualifiedName}.{node.Method.Name}'."); } - private PropertyInfo? TryGetPropertyFromMethod(MethodInfo method) + private static PropertyInfo? TryGetPropertyFromMethod(MethodInfo method) { var type = method.DeclaringType; return type?.GetRuntimeProperties().FirstOrDefault(prop => prop.GetMethod == method); diff --git a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs index cc6d92ceb7..34f8e568d4 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/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 { /// + [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 interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public IPropertyAccessor? Start(WeakReference reference, string propertyName) { _ = reference ?? throw new ArgumentNullException(nameof(reference)); diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs index 361d68dc81..bc300386b9 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs +++ b/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 { /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public bool Match(WeakReference reference, string memberName) { reference.TryGetTarget(out var target); @@ -24,11 +26,13 @@ namespace Avalonia.Data.Core.Plugins } /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner) { return new Accessor(reference, name, inner); } + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] private sealed class Accessor : DataValidationBase { private readonly ValidationContext? _context; @@ -63,7 +67,7 @@ namespace Avalonia.Data.Core.Plugins } } - private Exception CreateException(IList errors) + private static Exception CreateException(IList errors) { if (errors.Count == 1) { diff --git a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index bf4a0a88bd..2bb8da2c74 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs +++ b/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 { /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public bool Match(WeakReference reference, string memberName) => true; /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner) { return new Validator(reference, name, inner); diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs index 88e38a8d08..5b4d7cd3a1 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs +++ b/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 /// A weak reference to the object. /// The name of the member to validate. /// True if the plugin can handle the object; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] bool Match(WeakReference reference, string memberName); /// @@ -25,6 +27,7 @@ namespace Avalonia.Data.Core.Plugins /// An interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] IPropertyAccessor Start(WeakReference reference, string propertyName, IPropertyAccessor inner); diff --git a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs index f000427de3..04601bc8b2 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs +++ b/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 /// The object. /// The property name. /// True if the plugin can handle the property on the object; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] bool Match(object obj, string propertyName); /// @@ -25,6 +27,7 @@ namespace Avalonia.Data.Core.Plugins /// An interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] IPropertyAccessor? Start(WeakReference reference, string propertyName); } diff --git a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs index b741cfaca2..8bf2d6c3d6 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs +++ b/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 /// /// A weak reference to the value. /// True if the plugin can handle the value; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] bool Match(WeakReference reference); /// @@ -21,6 +23,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An observable that produces the output for the value. /// + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] IObservable Start(WeakReference reference); } } diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 1e7a0d5c8f..87a2f67ee8 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs +++ b/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 ); /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public bool Match(WeakReference reference, string memberName) { reference.TryGetTarget(out var target); @@ -26,6 +28,7 @@ namespace Avalonia.Data.Core.Plugins } /// + [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor accessor) { return new Validator(reference, name, accessor); @@ -108,7 +111,7 @@ namespace Avalonia.Data.Core.Plugins return target; } - private Exception GenerateException(IList errors) + private static Exception GenerateException(IList errors) { if (errors.Count == 1) { diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 91d69b5d3d..5b19e995cc 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/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?>(); /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null; /// @@ -28,6 +29,7 @@ namespace Avalonia.Data.Core.Plugins /// An interface through which future interactions with the /// property will be made. /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] public IPropertyAccessor? Start(WeakReference 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; diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 0d51a6ed36..2397ce483d 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/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 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; diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index 6232fa39a1..ebee4586db 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs +++ b/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 /// /// Handles binding to s for the '^' stream binding operator. /// + [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public class ObservableStreamPlugin : IStreamPlugin { static MethodInfo? observableSelect; @@ -17,6 +19,7 @@ namespace Avalonia.Data.Core.Plugins /// /// A weak reference to the value. /// True if the plugin can handle the value; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual bool Match(WeakReference reference) { reference.TryGetTarget(out var target); @@ -33,6 +36,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An observable that produces the output for the value. /// + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual IObservable Start(WeakReference 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)); diff --git a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs index 377ea9f275..5203aa9f57 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs +++ b/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 /// /// Handles binding to s for the '^' stream binding operator. /// + [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public class TaskStreamPlugin : IStreamPlugin { /// @@ -16,12 +18,13 @@ namespace Avalonia.Data.Core.Plugins /// /// A weak reference to the value. /// True if the plugin can handle the value; otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual bool Match(WeakReference reference) { reference.TryGetTarget(out var target); return target is Task; - } + } /// /// Starts producing output based on the specified value. @@ -30,6 +33,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An observable that produces the output for the value. /// + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] public virtual IObservable Start(WeakReference reference) { reference.TryGetTarget(out var target); @@ -59,7 +63,8 @@ namespace Avalonia.Data.Core.Plugins return Observable.Empty(); } - private IObservable HandleCompleted(Task task) + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] + private static IObservable HandleCompleted(Task task) { var resultProperty = task.GetType().GetRuntimeProperty("Result"); diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index c13c6fcbc8..1b79fed6e7 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/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; diff --git a/src/Avalonia.Base/Data/Core/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs index 133e6a75f1..6dc6d07184 100644 --- a/src/Avalonia.Base/Data/Core/StreamNode.cs +++ b/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; diff --git a/src/Avalonia.Base/Data/IBinding.cs b/src/Avalonia.Base/Data/IBinding.cs index 7d44bf09b5..01204c39c1 100644 --- a/src/Avalonia.Base/Data/IBinding.cs +++ b/src/Avalonia.Base/Data/IBinding.cs @@ -24,7 +24,7 @@ namespace Avalonia.Data /// A or null if the binding could not be resolved. /// InstancedBinding? Initiate( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false); diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs index 6a33d0c4fc..fcd179b9b2 100644 --- a/src/Avalonia.Base/Data/IndexerBinding.cs +++ b/src/Avalonia.Base/Data/IndexerBinding.cs @@ -3,7 +3,7 @@ public class IndexerBinding : IBinding { public IndexerBinding( - IAvaloniaObject source, + AvaloniaObject source, AvaloniaProperty property, BindingMode mode) { @@ -12,12 +12,12 @@ Mode = mode; } - private IAvaloniaObject Source { get; } + private AvaloniaObject Source { get; } public AvaloniaProperty Property { get; } private BindingMode Mode { get; } public InstancedBinding? Initiate( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false) diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index 9ff319eddb..a349486bf8 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -9,7 +9,7 @@ namespace Avalonia.Data /// /// Whereas an holds a description of a binding such as "Bind to the X /// property on a control's DataContext"; this class represents a binding that has been - /// *instanced* by calling + /// *instanced* by calling /// on a target object. /// public class InstancedBinding diff --git a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs index 2d88203f67..75c1e3432b 100644 --- a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs +++ b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs @@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics public interface IAvaloniaObjectDebug { /// - /// Gets the subscriber list for the + /// Gets the subscriber list for the /// event. /// /// diff --git a/src/Avalonia.Base/Diagnostics/TrimmingMessages.cs b/src/Avalonia.Base/Diagnostics/TrimmingMessages.cs new file mode 100644 index 0000000000..a893256d17 --- /dev/null +++ b/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 [NotClientImplementable] - public interface IDataContextProvider : IAvaloniaObject + public interface IDataContextProvider { /// /// Gets or sets the element's data context. diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs index 476a36f372..016ca9a6fb 100644 --- a/src/Avalonia.Base/IDirectPropertyAccessor.cs +++ b/src/Avalonia.Base/IDirectPropertyAccessor.cs @@ -24,13 +24,13 @@ namespace Avalonia /// /// The instance. /// The property value. - object? GetValue(IAvaloniaObject instance); + object? GetValue(AvaloniaObject instance); /// /// Sets the value of the property on the instance. /// /// The instance. /// The value. - void SetValue(IAvaloniaObject instance, object? value); + void SetValue(AvaloniaObject instance, object? value); } } diff --git a/src/Avalonia.Base/IStyledElement.cs b/src/Avalonia.Base/IStyledElement.cs deleted file mode 100644 index 4eed54de45..0000000000 --- a/src/Avalonia.Base/IStyledElement.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.ComponentModel; -using Avalonia.Controls; -using Avalonia.LogicalTree; -using Avalonia.Metadata; -using Avalonia.Styling; - -namespace Avalonia -{ - [NotClientImplementable] - public interface IStyledElement : - IStyleable, - IStyleHost, - ILogical, - IResourceHost, - IDataContextProvider, - ISupportInitialize - { - /// - /// Occurs when the control has finished initialization. - /// - event EventHandler? Initialized; - - /// - /// Gets a value that indicates whether the element has finished initialization. - /// - bool IsInitialized { get; } - - /// - /// Gets or sets the control's styling classes. - /// - new Classes Classes { get; set; } - - /// - /// Gets the control's logical parent. - /// - IStyledElement? Parent { get; } - } -} diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs index 60175ae588..59c66ed505 100644 --- a/src/Avalonia.Base/Input/AccessKeyHandler.cs +++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Avalonia.Interactivity; using Avalonia.VisualTree; @@ -23,7 +24,7 @@ namespace Avalonia.Input /// /// The registered access keys. /// - private readonly List> _registered = new List>(); + private readonly List<(string AccessKey, IInputElement Element)> _registered = new(); /// /// The window to which the handler belongs. @@ -108,12 +109,12 @@ namespace Avalonia.Input { var existing = _registered.FirstOrDefault(x => x.Item2 == element); - if (existing != null) + if (existing != default) { _registered.Remove(existing); } - _registered.Add(Tuple.Create(accessKey.ToString().ToUpper(), element)); + _registered.Add((accessKey.ToString().ToUpperInvariant(), element)); } /// @@ -143,7 +144,7 @@ namespace Avalonia.Input { // TODO: Use FocusScopes to store the current element and restore it when context menu is closed. // Save currently focused input element. - _restoreFocusElement = FocusManager.Instance?.Current; + _restoreFocusElement = FocusManager.Instance?.Current; // When Alt is pressed without a main menu, or with a closed main menu, show // access key markers in the window (i.e. "_File"). @@ -180,21 +181,22 @@ namespace Avalonia.Input { // If any other key is pressed with the Alt key held down, or the main menu is open, // find all controls who have registered that access key. - var text = e.Key.ToString().ToUpper(); + var text = e.Key.ToString(); var matches = _registered - .Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible) - .Select(x => x.Item2); + .Where(x => string.Equals(x.AccessKey, text, StringComparison.OrdinalIgnoreCase) + && x.Element.IsEffectivelyVisible) + .Select(x => x.Element); // If the menu is open, only match controls in the menu's visual tree. if (menuIsOpen) { - matches = matches.Where(x => x is not null && MainMenu!.IsVisualAncestorOf(x)); + matches = matches.Where(x => x is not null && ((Visual)MainMenu!).IsVisualAncestorOf((Visual)x)); } var match = matches.FirstOrDefault(); // If there was a match, raise the AccessKeyPressed event on it. - if (match != null) + if (match is not null) { match.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent)); e.Handled = true; diff --git a/src/Avalonia.Base/Input/DragDropDevice.cs b/src/Avalonia.Base/Input/DragDropDevice.cs index 3c91856dcd..04d4693352 100644 --- a/src/Avalonia.Base/Input/DragDropDevice.cs +++ b/src/Avalonia.Base/Input/DragDropDevice.cs @@ -11,20 +11,21 @@ namespace Avalonia.Input private Interactive? _lastTarget = null; - private Interactive? GetTarget(IInputRoot root, Point local) + private static Interactive? GetTarget(IInputRoot root, Point local) { - var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); + var hit = root.InputHitTest(local) as Visual; + var target = hit?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); if (target != null && DragDrop.GetAllowDrop(target)) return target; return null; } - private DragDropEffects RaiseDragEvent(Interactive? target, IInputRoot inputRoot, Point point, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers) + private static DragDropEffects RaiseDragEvent(Interactive? target, IInputRoot inputRoot, Point point, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers) { if (target == null) return DragDropEffects.None; - var p = inputRoot.TranslatePoint(point, target); + var p = ((Visual)inputRoot).TranslatePoint(point, target); if (!p.HasValue) return DragDropEffects.None; diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 41276e2f06..7a27c53023 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -15,7 +15,7 @@ namespace Avalonia.Input public KeyModifiers KeyModifiers { get; private set; } - public Point GetPosition(IVisual relativeTo) + public Point GetPosition(Visual relativeTo) { var point = new Point(0, 0); diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs index c7b5b8e27f..2bf666af44 100644 --- a/src/Avalonia.Base/Input/FocusManager.cs +++ b/src/Avalonia.Base/Input/FocusManager.cs @@ -185,7 +185,7 @@ namespace Avalonia.Input /// /// The element. /// True if the element can be focused. - private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible; + private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && IsVisible(e); /// /// Gets the focus scope ancestors of the specified control, traversing popups. @@ -198,14 +198,15 @@ namespace Avalonia.Input while (c != null) { - var scope = c as IFocusScope; - - if (scope != null && c.VisualRoot?.IsVisible == true) + if (c is IFocusScope scope && + c is Visual v && + v.VisualRoot is Visual root && + root.IsVisible) { yield return scope; } - c = c.GetVisualParent() ?? + c = (c as Visual)?.GetVisualParent() ?? ((c as IHostedVisualTreeRoot)?.Host as IInputElement); } } @@ -221,11 +222,11 @@ namespace Avalonia.Input return; var ev = (PointerPressedEventArgs)e; - var visual = (IVisual)sender; + var visual = (Visual)sender; if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { - IVisual? element = ev.Pointer?.Captured ?? e.Source as IInputElement; + Visual? element = ev.Pointer?.Captured as Visual ?? e.Source as Visual; while (element != null) { @@ -240,5 +241,7 @@ namespace Avalonia.Input } } } + + private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true; } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 54ef0b1a68..a202d6b5bc 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -35,8 +35,8 @@ namespace Avalonia.Input.GestureRecognizers if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical) { logical.SetParent(logicalParent); - if (recognizer is IStyleable styleableRecognizer - && _inputElement is IStyleable styleableParent) + if (recognizer is StyledElement styleableRecognizer + && _inputElement is StyledElement styleableParent) styleableRecognizer.Bind(StyledElement.TemplatedParentProperty, styleableParent.GetObservable(StyledElement.TemplatedParentProperty)); } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 4f5585ab6c..7bcb81767d 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -72,7 +72,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId(); - _trackedRootPoint = e.GetPosition(_target); + _trackedRootPoint = e.GetPosition((Visual?)_target); } } @@ -86,7 +86,7 @@ namespace Avalonia.Input.GestureRecognizers { if (e.Pointer == _tracking) { - var rootPoint = e.GetPosition(_target); + var rootPoint = e.GetPosition((Visual?)_target); if (!_scrolling) { if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index acd78515cd..1ea88fe824 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -43,7 +43,7 @@ namespace Avalonia.Input RoutedEvent.Register( "PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures)); - private static readonly WeakReference s_lastPress = new WeakReference(null); + private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; public static readonly RoutedEvent PullGestureEvent = @@ -60,32 +60,32 @@ namespace Avalonia.Input InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); } - public static void AddTappedHandler(IInteractive element, EventHandler handler) + public static void AddTappedHandler(Interactive element, EventHandler handler) { element.AddHandler(TappedEvent, handler); } - public static void AddDoubleTappedHandler(IInteractive element, EventHandler handler) + public static void AddDoubleTappedHandler(Interactive element, EventHandler handler) { element.AddHandler(DoubleTappedEvent, handler); } - public static void AddRightTappedHandler(IInteractive element, EventHandler handler) + public static void AddRightTappedHandler(Interactive element, EventHandler handler) { element.AddHandler(RightTappedEvent, handler); } - public static void RemoveTappedHandler(IInteractive element, EventHandler handler) + public static void RemoveTappedHandler(Interactive element, EventHandler handler) { element.RemoveHandler(TappedEvent, handler); } - public static void RemoveDoubleTappedHandler(IInteractive element, EventHandler handler) + public static void RemoveDoubleTappedHandler(Interactive element, EventHandler handler) { element.RemoveHandler(DoubleTappedEvent, handler); } - public static void RemoveRightTappedHandler(IInteractive element, EventHandler handler) + public static void RemoveRightTappedHandler(Interactive element, EventHandler handler) { element.RemoveHandler(RightTappedEvent, handler); } @@ -100,20 +100,22 @@ namespace Avalonia.Input if (ev.Route == RoutingStrategies.Bubble) { var e = (PointerPressedEventArgs)ev; - var visual = (IVisual)ev.Source; + var visual = (Visual)ev.Source; if (e.ClickCount % 2 == 1) { s_isDoubleTapped = false; s_lastPress.SetTarget(ev.Source); - s_lastPressPoint = e.GetPosition((IVisual)ev.Source); + s_lastPressPoint = e.GetPosition((Visual)ev.Source); } else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { - if (s_lastPress.TryGetTarget(out var target) && target == e.Source) + if (s_lastPress.TryGetTarget(out var target) && + target == e.Source && + e.Source is Interactive i) { s_isDoubleTapped = true; - e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); + i.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); } } } @@ -127,9 +129,10 @@ namespace Avalonia.Input if (s_lastPress.TryGetTarget(out var target) && target == e.Source && - e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right) + e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right && + e.Source is Interactive i) { - var point = e.GetCurrentPoint((IVisual)target); + var point = e.GetCurrentPoint((Visual)target); var settings = AvaloniaLocator.Current.GetService(); var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); var tapRect = new Rect(s_lastPressPoint, new Size()) @@ -139,13 +142,13 @@ namespace Avalonia.Input { if (e.InitialPressMouseButton == MouseButton.Right) { - e.Source.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); + i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); } //s_isDoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called. //This behaviour matches UWP behaviour. else if (s_isDoubleTapped == false) { - e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e)); + i.RaiseEvent(new TappedEventArgs(TappedEvent, e)); } } } diff --git a/src/Avalonia.Base/Input/IInputElement.cs b/src/Avalonia.Base/Input/IInputElement.cs index 43ac87d008..6c20d20b4d 100644 --- a/src/Avalonia.Base/Input/IInputElement.cs +++ b/src/Avalonia.Base/Input/IInputElement.cs @@ -2,9 +2,6 @@ using System; using System.Collections.Generic; using Avalonia.Interactivity; using Avalonia.Metadata; -using Avalonia.VisualTree; - -#nullable enable namespace Avalonia.Input { @@ -12,7 +9,7 @@ namespace Avalonia.Input /// Defines input-related functionality for a control. /// [NotClientImplementable] - public interface IInputElement : IInteractive, IVisual + public interface IInputElement { /// /// Occurs when the control receives focus. @@ -93,7 +90,12 @@ namespace Avalonia.Input /// value of this control and its parent controls. /// bool IsEffectivelyEnabled { get; } - + + /// + /// Gets a value indicating whether this control and all its parents are visible. + /// + bool IsEffectivelyVisible { get; } + /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. /// @@ -123,5 +125,32 @@ namespace Avalonia.Input /// Gets the key bindings for the element. /// List KeyBindings { get; } + + /// + /// Adds a handler for the specified routed event. + /// + /// The routed event. + /// The handler. + /// The routing strategies to listen to. + /// Whether handled events should also be listened for. + /// A disposable that terminates the event subscription. + void AddHandler( + RoutedEvent routedEvent, + Delegate handler, + RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, + bool handledEventsToo = false); + + /// + /// Removes a handler for the specified routed event. + /// + /// The routed event. + /// The handler. + void RemoveHandler(RoutedEvent routedEvent, Delegate handler); + + /// + /// Raises a routed event. + /// + /// The event args. + void RaiseEvent(RoutedEventArgs e); } } diff --git a/src/Avalonia.Base/Input/IMainMenu.cs b/src/Avalonia.Base/Input/IMainMenu.cs index 213a979c28..0010397c1a 100644 --- a/src/Avalonia.Base/Input/IMainMenu.cs +++ b/src/Avalonia.Base/Input/IMainMenu.cs @@ -9,7 +9,7 @@ namespace Avalonia.Input /// Defines the interface for a window's main menu. /// [NotClientImplementable] - public interface IMainMenu : IVisual + public interface IMainMenu { /// /// Gets a value indicating whether the menu is open. diff --git a/src/Avalonia.Base/Input/InputExtensions.cs b/src/Avalonia.Base/Input/InputExtensions.cs index cbe36583e6..73dcef9e95 100644 --- a/src/Avalonia.Base/Input/InputExtensions.cs +++ b/src/Avalonia.Base/Input/InputExtensions.cs @@ -12,7 +12,7 @@ namespace Avalonia.Input /// public static class InputExtensions { - private static readonly Func s_hitTestDelegate = IsHitTestVisible; + private static readonly Func s_hitTestDelegate = IsHitTestVisible; /// /// Returns the active input elements at a point on an . @@ -26,7 +26,8 @@ namespace Avalonia.Input { element = element ?? throw new ArgumentNullException(nameof(element)); - return element.GetVisualsAt(p, s_hitTestDelegate).Cast(); + return (element as Visual)?.GetVisualsAt(p, s_hitTestDelegate).Cast() ?? + Enumerable.Empty(); } /// @@ -39,7 +40,7 @@ namespace Avalonia.Input { element = element ?? throw new ArgumentNullException(nameof(element)); - return element.GetVisualAt(p, s_hitTestDelegate) as IInputElement; + return (element as Visual)?.GetVisualAt(p, s_hitTestDelegate) as IInputElement; } /// @@ -55,22 +56,22 @@ namespace Avalonia.Input public static IInputElement? InputHitTest( this IInputElement element, Point p, - Func filter) + Func filter) { element = element ?? throw new ArgumentNullException(nameof(element)); filter = filter ?? throw new ArgumentNullException(nameof(filter)); - return element.GetVisualAt(p, x => s_hitTestDelegate(x) && filter(x)) as IInputElement; + return (element as Visual)?.GetVisualAt(p, x => s_hitTestDelegate(x) && filter(x)) as IInputElement; } - private static bool IsHitTestVisible(IVisual visual) + private static bool IsHitTestVisible(Visual visual) { var element = visual as IInputElement; return element != null && - element.IsVisible && + visual.IsVisible && element.IsHitTestVisible && element.IsEffectivelyEnabled && - element.IsAttachedToVisualTree; + visual.IsAttachedToVisualTree; } } } diff --git a/src/Avalonia.Base/Input/KeyGesture.cs b/src/Avalonia.Base/Input/KeyGesture.cs index 1a6372d346..c6618fd550 100644 --- a/src/Avalonia.Base/Input/KeyGesture.cs +++ b/src/Avalonia.Base/Input/KeyGesture.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; using Avalonia.Utilities; @@ -143,7 +144,7 @@ namespace Avalonia.Input // TODO: Move that to external key parser private static Key ParseKey(string key) { - if (s_keySynonyms.TryGetValue(key.ToLower(), out Key rv)) + if (s_keySynonyms.TryGetValue(key.ToLower(CultureInfo.InvariantCulture), out Key rv)) return rv; return EnumHelper.Parse(key, true); @@ -166,7 +167,7 @@ namespace Avalonia.Input return EnumHelper.Parse(modifier.ToString(), true); } - private Key ResolveNumPadOperationKey(Key key) + private static Key ResolveNumPadOperationKey(Key key) { switch (key) { diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs index 0600b54618..26ff71a4e7 100644 --- a/src/Avalonia.Base/Input/KeyboardDevice.cs +++ b/src/Avalonia.Base/Input/KeyboardDevice.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Interactivity; -using Avalonia.VisualTree; namespace Avalonia.Input { @@ -26,7 +25,7 @@ namespace Avalonia.Input public IInputElement? FocusedElement => _focusedElement; - private void ClearFocusWithinAncestors(IInputElement? element) + private static void ClearFocusWithinAncestors(IInputElement? element) { var el = element; @@ -37,18 +36,21 @@ namespace Avalonia.Input ie.IsKeyboardFocusWithin = false; } - el = (IInputElement?)el.VisualParent; + el = (IInputElement?)(el as Visual)?.VisualParent; } } private void ClearFocusWithin(IInputElement element, bool clearRoot) { - foreach (var visual in element.VisualChildren) + if (element is Visual v) { - if (visual is IInputElement el && el.IsKeyboardFocusWithin) + foreach (var visual in v.VisualChildren) { - ClearFocusWithin(el, true); - break; + if (visual is IInputElement el && el.IsKeyboardFocusWithin) + { + ClearFocusWithin(el, true); + break; + } } } @@ -81,7 +83,7 @@ namespace Avalonia.Input break; } - el = el.VisualParent as IInputElement; + el = (el as Visual)?.VisualParent as IInputElement; } el = oldElement; @@ -100,18 +102,21 @@ namespace Avalonia.Input ie.IsKeyboardFocusWithin = true; } - el = el.VisualParent as IInputElement; + el = (el as Visual)?.VisualParent as IInputElement; } } private void ClearChildrenFocusWithin(IInputElement element, bool clearRoot) { - foreach (var visual in element.VisualChildren) + if (element is Visual v) { - if (visual is IInputElement el && el.IsKeyboardFocusWithin) + foreach (var visual in v.VisualChildren) { - ClearChildrenFocusWithin(el, true); - break; + if (visual is IInputElement el && el.IsKeyboardFocusWithin) + { + ClearChildrenFocusWithin(el, true); + break; + } } } @@ -128,11 +133,11 @@ namespace Avalonia.Input { if (element != FocusedElement) { - var interactive = FocusedElement as IInteractive; + var interactive = FocusedElement as Interactive; if (FocusedElement != null && - (!FocusedElement.IsAttachedToVisualTree || - _focusedRoot != element?.VisualRoot as IInputRoot) && + (!((Visual)FocusedElement).IsAttachedToVisualTree || + _focusedRoot != ((Visual?)element)?.VisualRoot as IInputRoot) && _focusedRoot != null) { ClearChildrenFocusWithin(_focusedRoot, true); @@ -140,14 +145,14 @@ namespace Avalonia.Input SetIsFocusWithin(FocusedElement, element); _focusedElement = element; - _focusedRoot = _focusedElement?.VisualRoot as IInputRoot; + _focusedRoot = ((Visual?)_focusedElement)?.VisualRoot as IInputRoot; interactive?.RaiseEvent(new RoutedEventArgs { RoutedEvent = InputElement.LostFocusEvent, }); - interactive = element as IInteractive; + interactive = element as Interactive; interactive?.RaiseEvent(new GotFocusEventArgs { @@ -191,8 +196,8 @@ namespace Avalonia.Input KeyModifiers = keyInput.Modifiers.ToKeyModifiers(), Source = element, }; - - IVisual? currentHandler = element; + + var currentHandler = element as Visual; while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown) { var bindings = (currentHandler as IInputElement)?.KeyBindings; diff --git a/src/Avalonia.Base/Input/KeyboardNavigation.cs b/src/Avalonia.Base/Input/KeyboardNavigation.cs index 042a9adf5f..9d989d96d6 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigation.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigation.cs @@ -68,7 +68,7 @@ namespace Avalonia.Input /// The tab index. public static void SetTabIndex(IInputElement element, int value) { - ((IAvaloniaObject)element).SetValue(TabIndexProperty, value); + ((AvaloniaObject)element).SetValue(TabIndexProperty, value); } /// diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs index f73203f6b7..b05d8f30bb 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs @@ -50,7 +50,7 @@ namespace Avalonia.Input element = element ?? throw new ArgumentNullException(nameof(element)); // If there's a custom keyboard navigation handler as an ancestor, use that. - var custom = element.FindAncestorOfType(true); + var custom = (element as Visual)?.FindAncestorOfType(true); if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) return ce; @@ -156,9 +156,9 @@ namespace Avalonia.Input NavigationDirection direction, [NotNullWhen(true)] out IInputElement? result) { - if (newElement is object) + if (newElement is Visual v) { - var customHandler = newElement.FindAncestorOfType(true); + var customHandler = v.FindAncestorOfType(true); if (customHandler is object) { diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 8c10799911..9b17e336d3 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; using Avalonia.Input.Raw; -using Avalonia.Interactivity; using Avalonia.Platform; using Avalonia.Utilities; -using Avalonia.VisualTree; namespace Avalonia.Input { @@ -34,7 +30,7 @@ namespace Avalonia.Input ProcessRawEvent(margs); } - int ButtonCount(PointerPointProperties props) + static int ButtonCount(PointerPointProperties props) { var rv = 0; if (props.IsLeftButtonPressed) @@ -106,9 +102,10 @@ namespace Avalonia.Input private void LeaveWindow() { + } - PointerPointProperties CreateProperties(RawPointerEventArgs args) + static PointerPointProperties CreateProperties(RawPointerEventArgs args) { return new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()); } @@ -141,7 +138,7 @@ namespace Avalonia.Input _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); - var e = new PointerPressedEventArgs(source, _pointer, root, p, timestamp, properties, inputModifiers, _clickCount); + var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; } @@ -161,7 +158,7 @@ namespace Avalonia.Input if (source is object) { - var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); source.RaiseEvent(e); @@ -181,7 +178,7 @@ namespace Avalonia.Input if (source is not null) { - var e = new PointerReleasedEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, + var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, _lastMouseDownButton); source?.RaiseEvent(e); @@ -204,7 +201,7 @@ namespace Avalonia.Input if (source is not null) { - var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); + var e = new PointerWheelEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; @@ -224,7 +221,7 @@ namespace Avalonia.Input if (source != null) { var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureMagnifyEvent, source, - _pointer, root, p, timestamp, props, inputModifiers, delta); + _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; @@ -244,7 +241,7 @@ namespace Avalonia.Input if (source != null) { var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureRotateEvent, source, - _pointer, root, p, timestamp, props, inputModifiers, delta); + _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; @@ -264,7 +261,7 @@ namespace Avalonia.Input if (source != null) { var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureSwipeEvent, source, - _pointer, root, p, timestamp, props, inputModifiers, delta); + _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; diff --git a/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs b/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs index 83bcd9187a..3cd4ca0d76 100644 --- a/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs +++ b/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs @@ -10,13 +10,21 @@ namespace Avalonia.Input.Navigation /// /// The element. /// True if the element can be focused. - public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible; + public static bool CanFocus(this IInputElement e) + { + var visible = (e as Visual)?.IsVisible ?? true; + return e.Focusable && e.IsEffectivelyEnabled && visible; + } /// /// Checks if descendants of the specified element can be focused. /// /// The element. /// True if descendants of the element can be focused. - public static bool CanFocusDescendants(this IInputElement e) => e.IsEffectivelyEnabled && e.IsVisible; + public static bool CanFocusDescendants(this IInputElement e) + { + var visible = (e as Visual)?.IsVisible ?? true; + return e.IsEffectivelyEnabled && visible; + } } } diff --git a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs index cfd45f8262..d218867cf2 100644 --- a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs @@ -212,9 +212,10 @@ namespace Avalonia.Input.Navigation if (!IsFocusScope(e)) { // Verify if focusedElement is a visual descendant of e - if (focusedElement is IVisual visualFocusedElement && + if (focusedElement is Visual visualFocusedElement && + e is Visual v && visualFocusedElement != e && - e.IsVisualAncestorOf(visualFocusedElement)) + v.IsVisualAncestorOf(visualFocusedElement)) { return focusedElement; } @@ -236,7 +237,7 @@ namespace Avalonia.Input.Navigation if (uiElement is null || IsVisibleAndEnabled(uiElement)) { - if (e is IVisual elementAsVisual) + if (e is Visual elementAsVisual) { var children = elementAsVisual.VisualChildren; var count = children.Count; @@ -272,7 +273,7 @@ namespace Avalonia.Input.Navigation if (uiElement == null || IsVisibleAndEnabled(uiElement)) { - var elementAsVisual = e as IVisual; + var elementAsVisual = e as Visual; if (elementAsVisual != null) { @@ -385,7 +386,7 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetNextSibling(IInputElement e) { - if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual) + if (GetParent(e) is Visual parentAsVisual && e is Visual elementAsVisual) { var children = parentAsVisual.VisualChildren; var count = children.Count; @@ -589,7 +590,7 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetPreviousSibling(IInputElement e) { - if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual) + if (GetParent(e) is Visual parentAsVisual && e is Visual elementAsVisual) { var children = parentAsVisual.VisualChildren; var count = children.Count; @@ -646,7 +647,7 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetParent(IInputElement e) { // For Visual - go up the visual parent chain until we find Visual. - if (e is IVisual v) + if (e is Visual v) return v.FindAncestorOfType(); // This will need to be implemented when we have non-visual input elements. @@ -669,6 +670,7 @@ namespace Avalonia.Input.Navigation } private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e); - private static bool IsVisibleAndEnabled(IInputElement e) => e.IsVisible && e.IsEnabled; + private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true; + private static bool IsVisibleAndEnabled(IInputElement e) => IsVisible(e) && e.IsEnabled; } } diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index 31f34566be..98da83c1ce 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -91,7 +91,7 @@ namespace Avalonia.Input _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); - var e = new PointerPressedEventArgs(source, pointer, root, p, timestamp, properties, inputModifiers, _clickCount); + var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; } @@ -99,7 +99,7 @@ namespace Avalonia.Input return false; } - private bool PenMove(Pointer pointer, ulong timestamp, + private static bool PenMove(Pointer pointer, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers, IInputElement? hitTest, Lazy?>? intermediatePoints) @@ -108,7 +108,7 @@ namespace Avalonia.Input if (source is not null) { - var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, root, + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); source.RaiseEvent(e); @@ -126,7 +126,7 @@ namespace Avalonia.Input if (source is not null) { - var e = new PointerReleasedEventArgs(source, pointer, root, p, timestamp, properties, inputModifiers, + var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _lastMouseDownButton); source?.RaiseEvent(e); diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index 521f847a0a..1fc46dd396 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -20,7 +20,8 @@ namespace Avalonia.Input.Platform WholeWordTextActionModifiers = wholeWordTextActionModifiers; Copy = new List { - new KeyGesture(Key.C, commandModifiers) + new KeyGesture(Key.C, commandModifiers), + new KeyGesture(Key.Insert, KeyModifiers.Control) }; Cut = new List { @@ -28,7 +29,8 @@ namespace Avalonia.Input.Platform }; Paste = new List { - new KeyGesture(Key.V, commandModifiers) + new KeyGesture(Key.V, commandModifiers), + new KeyGesture(Key.Insert, KeyModifiers.Shift) }; Undo = new List { diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 3012f07f6a..4713364f00 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -19,12 +19,12 @@ namespace Avalonia.Input public int Id { get; } - IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2) + static IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2) { - if (control1 == null || control2 == null) + if (control1 is not Visual c1 || control2 is not Visual c2) return null; - var seen = new HashSet(control1.GetSelfAndVisualAncestors().OfType()); - return control2.GetSelfAndVisualAncestors().OfType().FirstOrDefault(seen.Contains); + var seen = new HashSet(c1.GetSelfAndVisualAncestors().OfType()); + return c2.GetSelfAndVisualAncestors().OfType().FirstOrDefault(seen.Contains); } protected virtual void PlatformCapture(IInputElement? element) @@ -34,15 +34,15 @@ namespace Avalonia.Input public void Capture(IInputElement? control) { - if (Captured != null) - Captured.DetachedFromVisualTree -= OnCaptureDetached; + if (Captured is Visual v1) + v1.DetachedFromVisualTree -= OnCaptureDetached; var oldCapture = Captured; Captured = control; PlatformCapture(control); - if (oldCapture != null) + if (oldCapture is Visual v2) { var commonParent = FindCommonParent(control, oldCapture); - foreach (var notifyTarget in oldCapture.GetSelfAndVisualAncestors().OfType()) + foreach (var notifyTarget in v2.GetSelfAndVisualAncestors().OfType()) { if (notifyTarget == commonParent) break; @@ -50,11 +50,11 @@ namespace Avalonia.Input } } - if (Captured != null) - Captured.DetachedFromVisualTree += OnCaptureDetached; + if (Captured is Visual v3) + v3.DetachedFromVisualTree += OnCaptureDetached; } - IInputElement? GetNextCapture(IVisual parent) + static IInputElement? GetNextCapture(Visual parent) { return parent as IInputElement ?? parent.FindAncestorOfType(); } diff --git a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs index d5577d77af..af0fa83382 100644 --- a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs @@ -7,8 +7,8 @@ namespace Avalonia.Input { public Vector Delta { get; set; } - internal PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source, - IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, + internal PointerDeltaEventArgs(RoutedEvent routedEvent, object? source, + IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(routedEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index f84dec42cb..d736253728 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -8,15 +8,15 @@ namespace Avalonia.Input { public class PointerEventArgs : RoutedEventArgs { - private readonly IVisual? _rootVisual; + private readonly Visual? _rootVisual; private readonly Point _rootVisualPosition; private readonly PointerPointProperties _properties; private readonly Lazy?>? _previousPoints; internal PointerEventArgs(RoutedEvent routedEvent, - IInteractive? source, + object? source, IPointer pointer, - IVisual? rootVisual, Point rootVisualPosition, + Visual? rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers) @@ -30,11 +30,11 @@ namespace Avalonia.Input Timestamp = timestamp; KeyModifiers = modifiers; } - + internal PointerEventArgs(RoutedEvent routedEvent, - IInteractive? source, + object? source, IPointer pointer, - IVisual? rootVisual, Point rootVisualPosition, + Visual? rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, @@ -59,7 +59,7 @@ namespace Avalonia.Input /// public KeyModifiers KeyModifiers { get; } - private Point GetPosition(Point pt, IVisual? relativeTo) + private Point GetPosition(Point pt, Visual? relativeTo) { if (_rootVisual == null) return default; @@ -74,14 +74,14 @@ namespace Avalonia.Input /// /// The control. /// The pointer position in the control's coordinates. - public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); + public Point GetPosition(Visual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); /// /// Returns the PointerPoint associated with the current event /// /// The visual which coordinate system to use. Pass null for toplevel coordinate system /// - public PointerPoint GetCurrentPoint(IVisual? relativeTo) + public PointerPoint GetCurrentPoint(Visual? relativeTo) => new PointerPoint(Pointer, GetPosition(relativeTo), _properties); /// @@ -89,7 +89,7 @@ namespace Avalonia.Input /// /// The visual which coordinate system to use. Pass null for toplevel coordinate system /// - public IReadOnlyList GetIntermediatePoints(IVisual? relativeTo) + public IReadOnlyList GetIntermediatePoints(Visual? relativeTo) { var previousPoints = _previousPoints?.Value; if (previousPoints == null || previousPoints.Count == 0) @@ -125,9 +125,9 @@ namespace Avalonia.Input public class PointerPressedEventArgs : PointerEventArgs { internal PointerPressedEventArgs( - IInteractive source, + object source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, + Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, @@ -144,8 +144,8 @@ namespace Avalonia.Input public class PointerReleasedEventArgs : PointerEventArgs { internal PointerReleasedEventArgs( - IInteractive source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, ulong timestamp, + object source, IPointer pointer, + Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, MouseButton initialPressMouseButton) : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition, @@ -164,7 +164,7 @@ namespace Avalonia.Input { public IPointer Pointer { get; } - internal PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) + internal PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) { Pointer = pointer; Source = source; diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs index e367f9f74f..7781255d89 100644 --- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs +++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs @@ -44,7 +44,7 @@ namespace Avalonia.Input && _lastPointer is (var lastPointer, var lastPosition)) { _lastPointer = null; - ClearPointerOver(lastPointer, args.Root, 0, args.Root.PointToClient(lastPosition), + ClearPointerOver(lastPointer, args.Root, 0, PointToClient(args.Root, lastPosition), new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()), args.InputModifiers.ToKeyModifiers()); } @@ -64,14 +64,14 @@ namespace Avalonia.Input { if (_lastPointer is (var pointer, var position)) { - var clientPoint = _inputRoot.PointToClient(position); + var clientPoint = PointToClient(_inputRoot, position); if (dirtyRect.Contains(clientPoint)) { var element = pointer.Captured ?? _inputRoot.InputHitTest(clientPoint); SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); } - else if (!_inputRoot.Bounds.Contains(clientPoint)) + else if (!((Visual)_inputRoot).Bounds.Contains(clientPoint)) { ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); } @@ -82,7 +82,7 @@ namespace Avalonia.Input { if (_lastPointer is (var pointer, var position)) { - var clientPoint = _inputRoot.PointToClient(position); + var clientPoint = PointToClient(_inputRoot, position); ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); } _lastPointer = null; @@ -101,10 +101,10 @@ namespace Avalonia.Input // Do not pass rootVisual, when we have unknown position, // so GetPosition won't return invalid values. var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer, - position.HasValue ? root : null, position.HasValue ? position.Value : default, + position.HasValue ? root as Visual : null, position.HasValue ? position.Value : default, timestamp, properties, inputModifiers); - if (element != null && !element.IsAttachedToVisualTree) + if (element is Visual v && !v.IsAttachedToVisualTree) { // element has been removed from visual tree so do top down cleanup if (root.IsPointerOver) @@ -117,7 +117,7 @@ namespace Avalonia.Input e.Source = element; e.Handled = false; element.RaiseEvent(e); - element = (IInputElement?)element.VisualParent; + element = GetVisualParent(element); } root.PointerOverElement = null; @@ -127,14 +127,18 @@ namespace Avalonia.Input private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element, bool clearRoot) { - foreach (IInputElement el in element.VisualChildren) + if (element is Visual v) { - if (el.IsPointerOver) + foreach (IInputElement el in v.VisualChildren) { - ClearChildrenPointerOver(e, el, true); - break; + if (el.IsPointerOver) + { + ClearChildrenPointerOver(e, el, true); + break; + } } } + if (clearRoot) { e.Source = element; @@ -160,7 +164,7 @@ namespace Avalonia.Input } } - _lastPointer = (pointer, root.PointToScreen(position)); + _lastPointer = (pointer, ((Visual)root).PointToScreen(position)); } private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element, @@ -177,14 +181,14 @@ namespace Avalonia.Input branch = el; break; } - el = (IInputElement?)el.VisualParent; + el = GetVisualParent(el); } el = root.PointerOverElement; - var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, root, position, + var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, (Visual)root, position, timestamp, properties, inputModifiers); - if (el != null && branch != null && !el.IsAttachedToVisualTree) + if (el is Visual v && branch != null && !v.IsAttachedToVisualTree) { ClearChildrenPointerOver(e, branch, false); } @@ -194,7 +198,7 @@ namespace Avalonia.Input e.Source = el; e.Handled = false; el.RaiseEvent(e); - el = (IInputElement?)el.VisualParent; + el = GetVisualParent(el); } el = root.PointerOverElement = element; @@ -206,8 +210,18 @@ namespace Avalonia.Input e.Source = el; e.Handled = false; el.RaiseEvent(e); - el = (IInputElement?)el.VisualParent; + el = GetVisualParent(el); } } + + private static IInputElement? GetVisualParent(IInputElement e) + { + return (e as Visual)?.VisualParent as IInputElement; + } + + private static Point PointToClient(IInputRoot root, PixelPoint p) + { + return ((Visual)root).PointToClient(p); + } } } diff --git a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs index 8a0412659b..a3de0eaaea 100644 --- a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { public Vector Delta { get; set; } - internal PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + internal PointerWheelEventArgs(object source, IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, diff --git a/src/Avalonia.Base/Input/TappedEventArgs.cs b/src/Avalonia.Base/Input/TappedEventArgs.cs index 8af6164fc1..3e15c4843a 100644 --- a/src/Avalonia.Base/Input/TappedEventArgs.cs +++ b/src/Avalonia.Base/Input/TappedEventArgs.cs @@ -17,6 +17,6 @@ namespace Avalonia.Input public KeyModifiers KeyModifiers => lastPointerEventArgs.KeyModifiers; public ulong Timestamp => lastPointerEventArgs.Timestamp; - public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo); + public Point GetPosition(Visual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo); } } diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs index 325d745782..4a60f8a046 100644 --- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs @@ -16,7 +16,7 @@ namespace Avalonia.Input.TextInput /// /// The visual that's showing the text /// - IVisual TextViewVisual { get; } + Visual TextViewVisual { get; } /// /// Should be fired when text-hosting visual is changed /// diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index 4734224da4..8a3f5b968e 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -73,10 +73,13 @@ namespace Avalonia.Input.TextInput private void UpdateCursorRect() { - if (_im == null || _client == null || _focusedElement?.VisualRoot == null) + if (_im == null || + _client == null || + _focusedElement is not Visual v || + v.VisualRoot is not Visual root) return; - var transform = _focusedElement.TransformToVisual(_focusedElement.VisualRoot); + var transform = v.TransformToVisual(root); if (transform == null) _im.SetCursorRect(default); else @@ -95,7 +98,7 @@ namespace Avalonia.Input.TextInput return; _focusedElement = element; - var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod; + var inputMethod = ((element as Visual)?.VisualRoot as ITextInputMethodRoot)?.InputMethod; if (_im != inputMethod) { diff --git a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs index 325c72bcb5..09716b4246 100644 --- a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs +++ b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input.TextInput { class TransformTrackingHelper : IDisposable { - private IVisual? _visual; + private Visual? _visual; private bool _queuedForUpdate; private readonly EventHandler _propertyChangedHandler; private readonly List _propertyChangedSubscriptions = new List(); @@ -17,7 +17,7 @@ namespace Avalonia.Input.TextInput _propertyChangedHandler = PropertyChangedHandler; } - public void SetVisual(IVisual? visual) + public void SetVisual(Visual? visual) { Dispose(); _visual = visual; @@ -72,7 +72,7 @@ namespace Avalonia.Input.TextInput { Matrix? matrix = null; if (_visual != null && _visual.VisualRoot != null) - matrix = _visual.TransformToVisual(_visual.VisualRoot); + matrix = _visual.TransformToVisual((Visual)_visual.VisualRoot); if (Matrix != matrix) { Matrix = matrix; diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index c98944359d..125d5fc813 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -21,7 +21,7 @@ namespace Avalonia.Input private Rect _lastClickRect; private ulong _lastClickTime; - RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown) + static RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown) { var rv = modifiers &= RawInputModifiers.KeyboardMask; if (isLeftButtonDown) @@ -75,7 +75,7 @@ namespace Avalonia.Input } target.RaiseEvent(new PointerPressedEventArgs(target, pointer, - args.Root, args.Position, ev.Timestamp, + (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), keyModifier, _clickCount)); } @@ -86,7 +86,7 @@ namespace Avalonia.Input using (pointer) { target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, - args.Root, args.Position, ev.Timestamp, + (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind), keyModifier, MouseButton.Left)); } @@ -101,7 +101,7 @@ namespace Avalonia.Input if (args.Type == RawPointerEventType.TouchUpdate) { - target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root, + target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), keyModifier, args.IntermediatePoints)); diff --git a/src/Avalonia.Base/Interactivity/EventRoute.cs b/src/Avalonia.Base/Interactivity/EventRoute.cs index 8f826835da..3491465a68 100644 --- a/src/Avalonia.Base/Interactivity/EventRoute.cs +++ b/src/Avalonia.Base/Interactivity/EventRoute.cs @@ -43,7 +43,7 @@ namespace Avalonia.Interactivity /// `DynamicInvoke` on the handler. /// public void Add( - IInteractive target, + Interactive target, Delegate handler, RoutingStrategies routes, bool handledEventsToo = false, @@ -60,7 +60,7 @@ namespace Avalonia.Interactivity /// Adds a class handler to the route. /// /// The target on which the event should be raised. - public void AddClassHandler(IInteractive target) + public void AddClassHandler(Interactive target) { target = target ?? throw new ArgumentNullException(nameof(target)); @@ -73,7 +73,7 @@ namespace Avalonia.Interactivity /// /// The event source. /// The event args. - public void RaiseEvent(IInteractive source, RoutedEventArgs e) + public void RaiseEvent(Interactive source, RoutedEventArgs e) { source = source ?? throw new ArgumentNullException(nameof(source)); e = e ?? throw new ArgumentNullException(nameof(e)); @@ -125,7 +125,7 @@ namespace Avalonia.Interactivity throw new ArgumentException("Event source may not be null", nameof(e)); } - IInteractive? lastTarget = null; + Interactive? lastTarget = null; var start = 0; var end = _route.Count; var step = 1; @@ -177,7 +177,7 @@ namespace Avalonia.Interactivity private readonly struct RouteItem { public RouteItem( - IInteractive target, + Interactive target, Delegate? handler, Action? adapter, RoutingStrategies routes, @@ -190,7 +190,7 @@ namespace Avalonia.Interactivity HandledEventsToo = handledEventsToo; } - public IInteractive Target { get; } + public Interactive Target { get; } public Delegate? Handler { get; } public Action? Adapter { get; } public RoutingStrategies Routes { get; } diff --git a/src/Avalonia.Base/Interactivity/IInteractive.cs b/src/Avalonia.Base/Interactivity/IInteractive.cs deleted file mode 100644 index 980bf54f1f..0000000000 --- a/src/Avalonia.Base/Interactivity/IInteractive.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Avalonia.Metadata; - -#nullable enable - -namespace Avalonia.Interactivity -{ - /// - /// Interface for objects that raise routed events. - /// - [NotClientImplementable] - public interface IInteractive - { - /// - /// Gets the interactive parent of the object for bubbling and tunneling events. - /// - IInteractive? InteractiveParent { get; } - - /// - /// Adds a handler for the specified routed event. - /// - /// The routed event. - /// The handler. - /// The routing strategies to listen to. - /// Whether handled events should also be listened for. - /// A disposable that terminates the event subscription. - void AddHandler( - RoutedEvent routedEvent, - Delegate handler, - RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, - bool handledEventsToo = false); - - /// - /// Removes a handler for the specified routed event. - /// - /// The routed event. - /// The handler. - void RemoveHandler(RoutedEvent routedEvent, Delegate handler); - - /// - /// Adds the object's handlers for a routed event to an event route. - /// - /// The event. - /// The event route. - void AddToEventRoute(RoutedEvent routedEvent, EventRoute route); - - /// - /// Raises a routed event. - /// - /// The event args. - void RaiseEvent(RoutedEventArgs e); - } -} diff --git a/src/Avalonia.Base/Interactivity/Interactive.cs b/src/Avalonia.Base/Interactivity/Interactive.cs index 75f74ed3b3..821e00d784 100644 --- a/src/Avalonia.Base/Interactivity/Interactive.cs +++ b/src/Avalonia.Base/Interactivity/Interactive.cs @@ -10,14 +10,14 @@ namespace Avalonia.Interactivity /// /// Base class for objects that raise routed events. /// - public class Interactive : Layoutable, IInteractive + public class Interactive : Layoutable { private Dictionary>? _eventHandlers; /// /// Gets the interactive parent of the object for bubbling and tunneling events. /// - IInteractive? IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; + protected internal virtual Interactive? InteractiveParent => VisualParent as Interactive; /// /// Adds a handler for the specified routed event. @@ -125,21 +125,6 @@ namespace Avalonia.Interactivity route.RaiseEvent(this, e); } - void IInteractive.AddToEventRoute(RoutedEvent routedEvent, EventRoute route) - { - routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent)); - route = route ?? throw new ArgumentNullException(nameof(route)); - - if (_eventHandlers != null && - _eventHandlers.TryGetValue(routedEvent, out var subscriptions)) - { - foreach (var sub in subscriptions) - { - route.Add(this, sub.Handler, sub.Routes, sub.HandledEventsToo, sub.InvokeAdapter); - } - } - } - /// /// Builds an event route for a routed event. /// @@ -151,7 +136,7 @@ namespace Avalonia.Interactivity /// and should be avoided if there are no handlers for an event. In these cases you can call /// this method to build the event route and check the /// property to see if there are any handlers registered on the route. If there are, call - /// to raise the event. + /// to raise the event. /// protected EventRoute BuildEventRoute(RoutedEvent e) { @@ -163,7 +148,7 @@ namespace Avalonia.Interactivity if (e.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble) || e.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel)) { - IInteractive? element = this; + Interactive? element = this; while (element != null) { @@ -183,7 +168,7 @@ namespace Avalonia.Interactivity result.AddClassHandler(this); } - ((IInteractive)this).AddToEventRoute(e, result); + AddToEventRoute(e, result); } return result; @@ -202,6 +187,21 @@ namespace Avalonia.Interactivity subscriptions.Add(subscription); } + private void AddToEventRoute(RoutedEvent routedEvent, EventRoute route) + { + routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent)); + route = route ?? throw new ArgumentNullException(nameof(route)); + + if (_eventHandlers != null && + _eventHandlers.TryGetValue(routedEvent, out var subscriptions)) + { + foreach (var sub in subscriptions) + { + route.Add(this, sub.Handler, sub.Routes, sub.HandledEventsToo, sub.InvokeAdapter); + } + } + } + private readonly struct EventSubscription { public EventSubscription( diff --git a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs index ee6b623b5f..b12cf64fdf 100644 --- a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs +++ b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs @@ -5,7 +5,7 @@ using System.Reactive.Linq; namespace Avalonia.Interactivity { /// - /// Provides extension methods for the interface. + /// Provides extension methods for the interface. /// public static class InteractiveExtensions { @@ -19,7 +19,7 @@ namespace Avalonia.Interactivity /// The routing strategies to listen to. /// Whether handled events should also be listened for. /// A disposable that terminates the event subscription. - public static IDisposable AddDisposableHandler(this IInteractive o, RoutedEvent routedEvent, + public static IDisposable AddDisposableHandler(this Interactive o, RoutedEvent routedEvent, EventHandler handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) where TEventArgs : RoutedEventArgs @@ -31,6 +31,8 @@ namespace Avalonia.Interactivity state => state.instance.RemoveHandler(state.routedEvent, state.handler)); } + public static Interactive? GetInteractiveParent(this Interactive o) => o.InteractiveParent; + /// /// Gets an observable for a . /// @@ -42,7 +44,7 @@ namespace Avalonia.Interactivity /// An observable which fires each time the event is raised. /// public static IObservable GetObservable( - this IInteractive o, + this Interactive o, RoutedEvent routedEvent, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) diff --git a/src/Avalonia.Base/Interactivity/RoutedEvent.cs b/src/Avalonia.Base/Interactivity/RoutedEvent.cs index 37edb24cc1..7046871538 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEvent.cs @@ -116,7 +116,7 @@ namespace Avalonia.Interactivity public IDisposable AddClassHandler( Action handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, - bool handledEventsToo = false) where TTarget : class, IInteractive + bool handledEventsToo = false) where TTarget : Interactive { void Adapter(object? sender, RoutedEventArgs e) { diff --git a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs index 2b660e7080..17164bd333 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs @@ -28,7 +28,7 @@ namespace Avalonia.Interactivity /// /// The routed event associated with these event args. /// The source object that raised the routed event. - public RoutedEventArgs(RoutedEvent? routedEvent, IInteractive? source) + public RoutedEventArgs(RoutedEvent? routedEvent, object? source) { RoutedEvent = routedEvent; Source = source; @@ -55,6 +55,6 @@ namespace Avalonia.Interactivity /// /// Gets or sets the source object that raised the routed event. /// - public IInteractive? Source { get; set; } + public object? Source { get; set; } } } diff --git a/src/Avalonia.Base/Layout/AttachedLayout.cs b/src/Avalonia.Base/Layout/AttachedLayout.cs index ece8bbe805..2b24ed02b0 100644 --- a/src/Avalonia.Base/Layout/AttachedLayout.cs +++ b/src/Avalonia.Base/Layout/AttachedLayout.cs @@ -43,7 +43,7 @@ namespace Avalonia.Layout /// /// Initializes any per-container state the layout requires when it is attached to an - /// container. + /// container. /// /// /// The context object that facilitates communication between the layout and its host @@ -184,7 +184,7 @@ namespace Avalonia.Layout /// protected void InvalidateArrange() => ArrangeInvalidated?.Invoke(this, EventArgs.Empty); - private VirtualizingLayoutContext GetVirtualizingLayoutContext(LayoutContext context) + private static VirtualizingLayoutContext GetVirtualizingLayoutContext(LayoutContext context) { if (context is VirtualizingLayoutContext virtualizingContext) { diff --git a/src/Avalonia.Base/Layout/ElementManager.cs b/src/Avalonia.Base/Layout/ElementManager.cs index 048e8eebee..73d29510bd 100644 --- a/src/Avalonia.Base/Layout/ElementManager.cs +++ b/src/Avalonia.Base/Layout/ElementManager.cs @@ -13,7 +13,7 @@ namespace Avalonia.Layout { internal class ElementManager { - private readonly List _realizedElements = new List(); + private readonly List _realizedElements = new List(); private readonly List _realizedElementLayoutBounds = new List(); private int _firstRealizedDataIndex; private VirtualizingLayoutContext? _context; @@ -69,9 +69,9 @@ namespace Avalonia.Layout return IsVirtualizingContext ? _realizedElements.Count : _context!.ItemCount; } - public ILayoutable GetAt(int realizedIndex) + public Layoutable GetAt(int realizedIndex) { - ILayoutable? element; + Layoutable? element; if (IsVirtualizingContext) { @@ -99,7 +99,7 @@ namespace Avalonia.Layout return element; } - public void Add(ILayoutable element, int dataIndex) + public void Add(Layoutable element, int dataIndex) { if (_realizedElements.Count == 0) { @@ -110,7 +110,7 @@ namespace Avalonia.Layout _realizedElementLayoutBounds.Add(default); } - public void Insert(int realizedIndex, int dataIndex, ILayoutable? element) + public void Insert(int realizedIndex, int dataIndex, Layoutable? element) { if (realizedIndex == 0) { @@ -207,7 +207,7 @@ namespace Avalonia.Layout public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context!.ItemCount; - public ILayoutable? GetRealizedElement(int dataIndex) + public Layoutable? GetRealizedElement(int dataIndex) { return IsVirtualizingContext ? GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) : @@ -330,7 +330,7 @@ namespace Avalonia.Layout } } - public int GetElementDataIndex(ILayoutable suggestedAnchor) + public int GetElementDataIndex(Layoutable suggestedAnchor) { var it = _realizedElements.IndexOf(suggestedAnchor); return it != -1 ? GetDataIndexFromRealizedRangeIndex(it) : -1; diff --git a/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs index e6a95d1ffb..c46c4ca98c 100644 --- a/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs +++ b/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs @@ -144,7 +144,7 @@ namespace Avalonia.Layout } public Size MeasureElement( - ILayoutable element, + Layoutable element, int index, Size availableSize, VirtualizingLayoutContext context) @@ -512,9 +512,9 @@ namespace Avalonia.Layout private Rect EstimateExtent(Size availableSize, string? layoutId) { - ILayoutable? firstRealizedElement = null; + Layoutable? firstRealizedElement = null; Rect firstBounds = new Rect(); - ILayoutable? lastRealizedElement = null; + Layoutable? lastRealizedElement = null; Rect lastBounds = new Rect(); int firstDataIndex = -1; int lastDataIndex = -1; @@ -727,7 +727,7 @@ namespace Avalonia.Layout } } - public ILayoutable? GetElementIfRealized(int dataIndex) + public Layoutable? GetElementIfRealized(int dataIndex) { if (_elementManager.IsDataIndexRealized(dataIndex)) { @@ -737,7 +737,7 @@ namespace Avalonia.Layout return null; } - public bool TryAddElement0(ILayoutable element) + public bool TryAddElement0(Layoutable element) { if (_elementManager.GetRealizedElementCount() == 0) { diff --git a/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs b/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs index e3eae0caf7..cb1601799e 100644 --- a/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs +++ b/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs @@ -21,14 +21,14 @@ namespace Avalonia.Layout Rect Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds); void Algorithm_OnElementMeasured( - ILayoutable element, + Layoutable element, int index, Size availableSize, Size measureSize, diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs index 88d95c81fd..38d46557e1 100644 --- a/src/Avalonia.Base/Layout/ILayoutManager.cs +++ b/src/Avalonia.Base/Layout/ILayoutManager.cs @@ -18,13 +18,13 @@ namespace Avalonia.Layout /// Notifies the layout manager that a control requires a measure. /// /// The control. - void InvalidateMeasure(ILayoutable control); + void InvalidateMeasure(Layoutable control); /// /// Notifies the layout manager that a control requires an arrange. /// /// The control. - void InvalidateArrange(ILayoutable control); + void InvalidateArrange(Layoutable control); /// /// Executes a layout pass. @@ -48,12 +48,12 @@ namespace Avalonia.Layout /// Registers a control as wanting to receive effective viewport notifications. /// /// The control. - void RegisterEffectiveViewportListener(ILayoutable control); + void RegisterEffectiveViewportListener(Layoutable control); /// /// Registers a control as no longer wanting to receive effective viewport notifications. /// /// The control. - void UnregisterEffectiveViewportListener(ILayoutable control); + void UnregisterEffectiveViewportListener(Layoutable control); } } diff --git a/src/Avalonia.Base/Layout/ILayoutRoot.cs b/src/Avalonia.Base/Layout/ILayoutRoot.cs index df15fc1a1d..c90ddb9219 100644 --- a/src/Avalonia.Base/Layout/ILayoutRoot.cs +++ b/src/Avalonia.Base/Layout/ILayoutRoot.cs @@ -6,7 +6,7 @@ namespace Avalonia.Layout /// Defines the root of a layoutable tree. /// [NotClientImplementable] - public interface ILayoutRoot : ILayoutable + public interface ILayoutRoot { /// /// The size available to lay out the controls. diff --git a/src/Avalonia.Base/Layout/ILayoutable.cs b/src/Avalonia.Base/Layout/ILayoutable.cs deleted file mode 100644 index d8b546b04a..0000000000 --- a/src/Avalonia.Base/Layout/ILayoutable.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Avalonia.Metadata; -using Avalonia.VisualTree; - -namespace Avalonia.Layout -{ - /// - /// Defines layout-related functionality for a control. - /// - [NotClientImplementable] - public interface ILayoutable : IVisual - { - /// - /// Gets the size that this element computed during the measure pass of the layout process. - /// - Size DesiredSize { get; } - - /// - /// Gets the width of the element. - /// - double Width { get; } - - /// - /// Gets the height of the element. - /// - double Height { get; } - - /// - /// Gets the minimum width of the element. - /// - double MinWidth { get; } - - /// - /// Gets the maximum width of the element. - /// - double MaxWidth { get; } - - /// - /// Gets the minimum height of the element. - /// - double MinHeight { get; } - - /// - /// Gets the maximum height of the element. - /// - double MaxHeight { get; } - - /// - /// Gets the margin around the element. - /// - Thickness Margin { get; } - - /// - /// Gets the element's preferred horizontal alignment in its parent. - /// - HorizontalAlignment HorizontalAlignment { get; } - - /// - /// Gets the element's preferred vertical alignment in its parent. - /// - VerticalAlignment VerticalAlignment { get; } - - /// - /// Gets a value indicating whether the control's layout measure is valid. - /// - bool IsMeasureValid { get; } - - /// - /// Gets a value indicating whether the control's layouts arrange is valid. - /// - bool IsArrangeValid { get; } - - /// - /// Gets the available size passed in the previous layout pass, if any. - /// - Size? PreviousMeasure { get; } - - /// - /// Gets the layout rect passed in the previous layout pass, if any. - /// - Rect? PreviousArrange { get; } - - /// - /// Creates the visual children of the control, if necessary - /// - void ApplyTemplate(); - - /// - /// Carries out a measure of the control. - /// - /// The available size for the control. - void Measure(Size availableSize); - - /// - /// Arranges the control and its children. - /// - /// The control's new bounds. - void Arrange(Rect rect); - - /// - /// Invalidates the measurement of the control and queues a new layout pass. - /// - void InvalidateMeasure(); - - /// - /// Invalidates the arrangement of the control and queues a new layout pass. - /// - void InvalidateArrange(); - - /// - /// Called when a child control's desired size changes. - /// - /// The child control. - void ChildDesiredSizeChanged(ILayoutable control); - - /// - /// Used by the to notify the control that its effective - /// viewport is changed. - /// - /// The viewport information. - void EffectiveViewportChanged(EffectiveViewportChangedEventArgs e); - } -} diff --git a/src/Avalonia.Base/Layout/LayoutContextAdapter.cs b/src/Avalonia.Base/Layout/LayoutContextAdapter.cs index 25132c2802..e347e2be8b 100644 --- a/src/Avalonia.Base/Layout/LayoutContextAdapter.cs +++ b/src/Avalonia.Base/Layout/LayoutContextAdapter.cs @@ -38,8 +38,8 @@ namespace Avalonia.Layout protected override int ItemCountCore() => _nonVirtualizingContext.Children.Count; protected override object GetItemAtCore(int index) => _nonVirtualizingContext.Children[index]; - protected override ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options) => + protected override Layoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options) => _nonVirtualizingContext.Children[index]; - protected override void RecycleElementCore(ILayoutable element) { } + protected override void RecycleElementCore(Layoutable element) { } } } diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs index 0851dbaea9..c6cbda6255 100644 --- a/src/Avalonia.Base/Layout/LayoutHelper.cs +++ b/src/Avalonia.Base/Layout/LayoutHelper.cs @@ -16,15 +16,15 @@ namespace Avalonia.Layout public static double LayoutEpsilon { get; } = 0.00000153; /// - /// Calculates a control's size based on its , - /// , , - /// , and - /// . + /// Calculates a control's size based on its , + /// , , + /// , and + /// . /// /// The control. /// The space available for the control. /// The control's size. - public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints) + public static Size ApplyLayoutConstraints(Layoutable control, Size constraints) { var minmax = new MinMax(control); @@ -33,7 +33,7 @@ namespace Avalonia.Layout MathUtilities.Clamp(constraints.Height, minmax.MinHeight, minmax.MaxHeight)); } - public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding, + public static Size MeasureChild(Layoutable? control, Size availableSize, Thickness padding, Thickness borderThickness) { if (IsParentLayoutRounded(control, out double scale)) @@ -51,7 +51,7 @@ namespace Avalonia.Layout return new Size().Inflate(padding + borderThickness); } - public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding) + public static Size MeasureChild(Layoutable? control, Size availableSize, Thickness padding) { if (IsParentLayoutRounded(control, out double scale)) { @@ -67,7 +67,7 @@ namespace Avalonia.Layout return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top); } - public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding, Thickness borderThickness) + public static Size ArrangeChild(Layoutable? child, Size availableSize, Thickness padding, Thickness borderThickness) { if (IsParentLayoutRounded(child, out double scale)) { @@ -78,7 +78,7 @@ namespace Avalonia.Layout return ArrangeChildInternal(child, availableSize, padding + borderThickness); } - public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding) + public static Size ArrangeChild(Layoutable? child, Size availableSize, Thickness padding) { if(IsParentLayoutRounded(child, out double scale)) padding = RoundLayoutThickness(padding, scale, scale); @@ -86,18 +86,18 @@ namespace Avalonia.Layout return ArrangeChildInternal(child, availableSize, padding); } - private static Size ArrangeChildInternal(ILayoutable? child, Size availableSize, Thickness padding) + private static Size ArrangeChildInternal(Layoutable? child, Size availableSize, Thickness padding) { child?.Arrange(new Rect(availableSize).Deflate(padding)); return availableSize; } - private static bool IsParentLayoutRounded(ILayoutable? child, out double scale) + private static bool IsParentLayoutRounded(Layoutable? child, out double scale) { - var layoutableParent = (ILayoutable?)child?.GetVisualParent(); + var layoutableParent = (child as Visual)?.GetVisualParent() as Layoutable; - if (layoutableParent == null || !((Layoutable)layoutableParent).UseLayoutRounding) + if (layoutableParent == null || !layoutableParent.UseLayoutRounding) { scale = 1.0; return false; @@ -110,11 +110,11 @@ namespace Avalonia.Layout /// /// Invalidates measure for given control and all visual children recursively. /// - public static void InvalidateSelfAndChildrenMeasure(ILayoutable control) + public static void InvalidateSelfAndChildrenMeasure(Layoutable control) { - void InnerInvalidateMeasure(IVisual target) + void InnerInvalidateMeasure(Visual target) { - if (target is ILayoutable targetLayoutable) + if (target is Layoutable targetLayoutable) { targetLayoutable.InvalidateMeasure(); } @@ -124,13 +124,14 @@ namespace Avalonia.Layout for (int i = 0; i < visualChildrenCount; i++) { - IVisual child = visualChildren[i]; + Visual child = visualChildren[i]; InnerInvalidateMeasure(child); } } - InnerInvalidateMeasure(control); + if (control is Visual v) + InnerInvalidateMeasure(v); } /// @@ -138,9 +139,9 @@ namespace Avalonia.Layout /// /// The control. /// Thrown when control has no root or returned layout scaling is invalid. - public static double GetLayoutScale(ILayoutable control) + public static double GetLayoutScale(Layoutable control) { - var visualRoot = control.VisualRoot; + var visualRoot = (control as Visual)?.VisualRoot; var result = (visualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; @@ -289,7 +290,7 @@ namespace Avalonia.Layout /// private readonly struct MinMax { - public MinMax(ILayoutable e) + public MinMax(Layoutable e) { MaxHeight = e.MaxHeight; MinHeight = e.MinHeight; diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index 826947c39a..242d70821a 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -16,9 +16,9 @@ namespace Avalonia.Layout public class LayoutManager : ILayoutManager, IDisposable { private const int MaxPasses = 3; - private readonly ILayoutRoot _owner; - private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); - private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); + private readonly Layoutable _owner; + private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); + private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private readonly Action _executeLayoutPass; private List? _effectiveViewportChangedListeners; private bool _disposed; @@ -27,14 +27,14 @@ namespace Avalonia.Layout public LayoutManager(ILayoutRoot owner) { - _owner = owner ?? throw new ArgumentNullException(nameof(owner)); + _owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner)); _executeLayoutPass = ExecuteQueuedLayoutPass; } public virtual event EventHandler? LayoutUpdated; /// - public virtual void InvalidateMeasure(ILayoutable control) + public virtual void InvalidateMeasure(Layoutable control) { control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); @@ -65,7 +65,7 @@ namespace Avalonia.Layout } /// - public virtual void InvalidateArrange(ILayoutable control) + public virtual void InvalidateArrange(Layoutable control) { control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); @@ -203,7 +203,7 @@ namespace Avalonia.Layout _toArrange.Dispose(); } - void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control) + void ILayoutManager.RegisterEffectiveViewportListener(Layoutable control) { _effectiveViewportChangedListeners ??= new List(); _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener( @@ -211,7 +211,7 @@ namespace Avalonia.Layout CalculateEffectiveViewport(control))); } - void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control) + void ILayoutManager.UnregisterEffectiveViewportListener(Layoutable control) { if (_effectiveViewportChangedListeners is object) { @@ -265,13 +265,13 @@ namespace Avalonia.Layout } } - private void Measure(ILayoutable control) + private void Measure(Layoutable control) { // Controls closest to the visual root need to be arranged first. We don't try to store // ordered invalidation lists, instead we traverse the tree upwards, measuring the // controls closest to the root first. This has been shown by benchmarks to be the // fastest and most memory-efficient algorithm. - if (control.VisualParent is ILayoutable parent) + if (control.VisualParent is Layoutable parent) { Measure(parent); } @@ -283,7 +283,7 @@ namespace Avalonia.Layout { if (control is ILayoutRoot root) { - root.Measure(Size.Infinity); + control.Measure(Size.Infinity); } else if (control.PreviousMeasure.HasValue) { @@ -292,9 +292,9 @@ namespace Avalonia.Layout } } - private void Arrange(ILayoutable control) + private void Arrange(Layoutable control) { - if (control.VisualParent is ILayoutable parent) + if (control.VisualParent is Layoutable parent) { Arrange(parent); } @@ -304,7 +304,7 @@ namespace Avalonia.Layout if (control is IEmbeddedLayoutRoot embeddedRoot) control.Arrange(new Rect(embeddedRoot.AllocatedSize)); else if (control is ILayoutRoot root) - control.Arrange(new Rect(root.DesiredSize)); + control.Arrange(new Rect(control.DesiredSize)); else if (control.PreviousArrange != null) { // Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else. @@ -350,7 +350,7 @@ namespace Avalonia.Layout if (viewport != l.Viewport) { - l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); + l.Listener.RaiseEffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); l.Viewport = viewport; } } @@ -364,14 +364,14 @@ namespace Avalonia.Layout return startCount != _toMeasure.Count + _toArrange.Count; } - private Rect CalculateEffectiveViewport(IVisual control) + private Rect CalculateEffectiveViewport(Visual control) { var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity); CalculateEffectiveViewport(control, control, ref viewport); return viewport; } - private void CalculateEffectiveViewport(IVisual target, IVisual control, ref Rect viewport) + private void CalculateEffectiveViewport(Visual target, Visual control, ref Rect viewport) { // Recurse until the top level control. if (control.VisualParent is object) @@ -405,13 +405,13 @@ namespace Avalonia.Layout private class EffectiveViewportChangedListener { - public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport) + public EffectiveViewportChangedListener(Layoutable listener, Rect viewport) { Listener = listener; Viewport = viewport; } - public ILayoutable Listener { get; } + public Layoutable Listener { get; } public Rect Viewport { get; set; } } } diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index 527b63292d..d185148894 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Logging; +using Avalonia.Styling; using Avalonia.VisualTree; #nullable enable @@ -61,7 +62,7 @@ namespace Avalonia.Layout /// /// Implements layout-related functionality for a control. /// - public class Layoutable : Visual, ILayoutable + public class Layoutable : Visual { /// /// Defines the property. @@ -322,15 +323,12 @@ namespace Avalonia.Layout set { SetValue(UseLayoutRoundingProperty, value); } } - /// - /// Gets the available size passed in the previous layout pass, if any. - /// - Size? ILayoutable.PreviousMeasure => _previousMeasure; + internal Size? PreviousMeasure => _previousMeasure; /// /// Gets the layout rect passed in the previous layout pass, if any. /// - Rect? ILayoutable.PreviousArrange => _previousArrange; + internal Rect? PreviousArrange => _previousArrange; /// /// Creates the visual children of the control, if necessary @@ -379,7 +377,7 @@ namespace Avalonia.Layout if (DesiredSize != previousDesiredSize) { - this.GetVisualParent()?.ChildDesiredSizeChanged(this); + this.GetVisualParent()?.ChildDesiredSizeChanged(this); } } } @@ -422,7 +420,7 @@ namespace Avalonia.Layout IsMeasureValid = false; IsArrangeValid = false; - if (((ILayoutable)this).IsAttachedToVisualTree) + if (IsAttachedToVisualTree) { (VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this); InvalidateVisual(); @@ -447,7 +445,7 @@ namespace Avalonia.Layout } /// - void ILayoutable.ChildDesiredSizeChanged(ILayoutable control) + internal void ChildDesiredSizeChanged(Layoutable control) { if (!_measuring) { @@ -455,11 +453,11 @@ namespace Avalonia.Layout } } - void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e) + internal void RaiseEffectiveViewportChanged(EffectiveViewportChangedEventArgs e) { _effectiveViewportChanged?.Invoke(this, e); } - + /// /// Marks a property as affecting the control's measurement. /// @@ -470,7 +468,7 @@ namespace Avalonia.Layout /// property will cause to be called on the element. /// protected static void AffectsMeasure(params AvaloniaProperty[] properties) - where T : class, ILayoutable + where T : Layoutable { void Invalidate(AvaloniaPropertyChangedEventArgs e) { @@ -493,7 +491,7 @@ namespace Avalonia.Layout /// property will cause to be called on the element. /// protected static void AffectsArrange(params AvaloniaProperty[] properties) - where T : class, ILayoutable + where T : Layoutable { void Invalidate(AvaloniaPropertyChangedEventArgs e) { @@ -595,9 +593,9 @@ namespace Avalonia.Layout for (var i = 0; i < visualCount; i++) { - IVisual visual = visualChildren[i]; + Visual visual = visualChildren[i]; - if (visual is ILayoutable layoutable) + if (visual is Layoutable layoutable) { layoutable.Measure(availableSize); width = Math.Max(width, layoutable.DesiredSize.Width); @@ -708,9 +706,9 @@ namespace Avalonia.Layout for (var i = 0; i < visualCount; i++) { - IVisual visual = visualChildren[i]; + Visual visual = visualChildren[i]; - if (visual is ILayoutable layoutable) + if (visual is Layoutable layoutable) { layoutable.Arrange(arrangeRect); } @@ -719,9 +717,9 @@ namespace Avalonia.Layout return finalSize; } - protected sealed override void InvalidateStyles() + internal sealed override void InvalidateStyles(bool recurse) { - base.InvalidateStyles(); + base.InvalidateStyles(recurse); InvalidateMeasure(); } @@ -777,7 +775,7 @@ namespace Avalonia.Layout DesiredSize = default; // All changes to visibility cause the parent element to be notified. - this.GetVisualParent()?.ChildDesiredSizeChanged(this); + this.GetVisualParent()?.ChildDesiredSizeChanged(this); // We only invalidate outselves when visibility is changed to true. if (change.GetNewValue()) @@ -788,13 +786,19 @@ namespace Avalonia.Layout } /// - protected sealed override void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) + protected sealed override void OnVisualParentChanged(Visual? oldParent, Visual? newParent) { LayoutHelper.InvalidateSelfAndChildrenMeasure(this); base.OnVisualParentChanged(oldParent, newParent); } + private protected override void OnControlThemeChanged() + { + base.OnControlThemeChanged(); + InvalidateMeasure(); + } + /// /// Called when the layout manager raises a LayoutUpdated event. /// diff --git a/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs b/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs index 91348f5e6d..78ce5c480e 100644 --- a/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs +++ b/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs @@ -17,13 +17,13 @@ namespace Avalonia.Layout /// /// Gets the collection of child controls from the container that provides the context. /// - public IReadOnlyList Children => ChildrenCore; + public IReadOnlyList Children => ChildrenCore; /// /// Implements the behavior for getting the return value of in a /// derived or custom . /// - protected abstract IReadOnlyList ChildrenCore { get; } + protected abstract IReadOnlyList ChildrenCore { get; } internal VirtualizingLayoutContext GetVirtualizingContextAdapter() => _contextAdapter ??= new LayoutContextAdapter(this); diff --git a/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs b/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs index 6fa8601916..72ff1780ab 100644 --- a/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs +++ b/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs @@ -59,7 +59,7 @@ namespace Avalonia.Layout { var element = context.Children[i]; - if (!element.IsVisible) + if ((element as Visual)?.IsVisible == false) { continue; } @@ -100,7 +100,7 @@ namespace Avalonia.Layout { var element = context.Children[i]; - if (!element.IsVisible) + if ((element as Visual)?.IsVisible == false) { continue; } @@ -117,7 +117,7 @@ namespace Avalonia.Layout Math.Max(finalSize.Height, bounds.Height)); } - private static Rect LayoutVertical(ILayoutable element, double y, Size constraint) + private static Rect LayoutVertical(Layoutable element, double y, Size constraint) { var x = 0.0; var width = element.DesiredSize.Width; @@ -138,7 +138,7 @@ namespace Avalonia.Layout return new Rect(x, y, width, element.DesiredSize.Height); } - private static Rect LayoutHorizontal(ILayoutable element, double x, Size constraint) + private static Rect LayoutHorizontal(Layoutable element, double x, Size constraint) { var y = 0.0; var height = element.DesiredSize.Height; diff --git a/src/Avalonia.Base/Layout/StackLayout.cs b/src/Avalonia.Base/Layout/StackLayout.cs index e3c2ab3817..e9093cc146 100644 --- a/src/Avalonia.Base/Layout/StackLayout.cs +++ b/src/Avalonia.Base/Layout/StackLayout.cs @@ -78,10 +78,10 @@ namespace Avalonia.Layout internal Rect GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -121,7 +121,7 @@ namespace Avalonia.Layout } internal void OnElementMeasured( - ILayoutable element, + Layoutable element, int index, Size availableSize, Size measureSize, @@ -188,10 +188,10 @@ namespace Avalonia.Layout Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -206,7 +206,7 @@ namespace Avalonia.Layout lastRealizedLayoutBounds); } - void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(ILayoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context) + void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(Layoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context) { OnElementMeasured( element, @@ -335,7 +335,7 @@ namespace Avalonia.Layout InvalidateLayout(); } - private double GetAverageElementSize( + private static double GetAverageElementSize( Size availableSize, VirtualizingLayoutContext context, StackLayoutState stackLayoutState) @@ -359,6 +359,6 @@ namespace Avalonia.Layout private void InvalidateLayout() => InvalidateMeasure(); - private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((StackLayoutState)context.LayoutState!).FlowAlgorithm; + private static FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((StackLayoutState)context.LayoutState!).FlowAlgorithm; } } diff --git a/src/Avalonia.Base/Layout/UniformGridLayout.cs b/src/Avalonia.Base/Layout/UniformGridLayout.cs index a7880a1545..c539396e75 100644 --- a/src/Avalonia.Base/Layout/UniformGridLayout.cs +++ b/src/Avalonia.Base/Layout/UniformGridLayout.cs @@ -338,10 +338,10 @@ namespace Avalonia.Layout Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -391,7 +391,7 @@ namespace Avalonia.Layout return extent; } - void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(ILayoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context) + void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(Layoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context) { } @@ -557,6 +557,6 @@ namespace Avalonia.Layout private void InvalidateLayout() => InvalidateMeasure(); - private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((UniformGridLayoutState)context.LayoutState!).FlowAlgorithm; + private static FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((UniformGridLayoutState)context.LayoutState!).FlowAlgorithm; } } diff --git a/src/Avalonia.Base/Layout/UniformGridLayoutState.cs b/src/Avalonia.Base/Layout/UniformGridLayoutState.cs index 65cdf4c94b..fe2fe474da 100644 --- a/src/Avalonia.Base/Layout/UniformGridLayoutState.cs +++ b/src/Avalonia.Base/Layout/UniformGridLayoutState.cs @@ -18,7 +18,7 @@ namespace Avalonia.Layout // If it does not, then we need to do context.GetElement(0) at which point we have requested an element and are on point to clear it. // If we are responsible for clearing element 0 we keep m_cachedFirstElement valid. // If we are not (because FlowLayoutAlgorithm is holding it for us) then we just null out this field and use the one from FlowLayoutAlgorithm. - private ILayoutable? _cachedFirstElement; + private Layoutable? _cachedFirstElement; internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm(); internal double EffectiveItemWidth { get; private set; } @@ -91,7 +91,7 @@ namespace Avalonia.Layout } private void SetSize( - ILayoutable element, + Layoutable element, double layoutItemWidth, double layoutItemHeight, Size availableSize, diff --git a/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs b/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs index f068187523..cc8cca5498 100644 --- a/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs +++ b/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs @@ -19,18 +19,18 @@ namespace Avalonia.Layout set => _virtualizingContext.LayoutState = value; } - protected override IReadOnlyList ChildrenCore => + protected override IReadOnlyList ChildrenCore => _children ?? (_children = new ChildrenCollection(_virtualizingContext)); - private class ChildrenCollection : IReadOnlyList + private class ChildrenCollection : IReadOnlyList { private readonly VirtualizingLayoutContext _context; public ChildrenCollection(VirtualizingLayoutContext context) => _context = context; - public ILayoutable this[int index] => _context.GetOrCreateElementAt(index); + public Layoutable this[int index] => _context.GetOrCreateElementAt(index); public int Count => _context.ItemCount; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { for (var i = 0; i < Count; ++i) { diff --git a/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs b/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs index f48c9b0ef2..7fbef19d16 100644 --- a/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs +++ b/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs @@ -116,7 +116,7 @@ namespace Avalonia.Layout /// This method calls /// with options set to None. GetElementAtCore must be implemented in a derived class. /// - public ILayoutable GetOrCreateElementAt(int index) + public Layoutable GetOrCreateElementAt(int index) => GetOrCreateElementAtCore(index, ElementRealizationOptions.None); /// @@ -140,7 +140,7 @@ namespace Avalonia.Layout /// advanced layouts that choose to explicitly manage the realization and recycling of /// elements as a performance optimization. /// - public ILayoutable GetOrCreateElementAt(int index, ElementRealizationOptions options) + public Layoutable GetOrCreateElementAt(int index, ElementRealizationOptions options) => GetOrCreateElementAtCore(index, options); /// @@ -148,10 +148,10 @@ namespace Avalonia.Layout /// /// The element to clear. /// - /// This method calls , which must be implemented + /// This method calls , which must be implemented /// in a derived class. /// - public void RecycleElement(ILayoutable element) => RecycleElementCore(element); + public void RecycleElement(Layoutable element) => RecycleElementCore(element); /// /// When implemented in a derived class, retrieves the number of items in the data. @@ -180,14 +180,14 @@ namespace Avalonia.Layout /// A value of that specifies whether to suppress /// automatic recycling of the retrieved element or force creation of a new element. /// - protected abstract ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options); + protected abstract Layoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options); /// /// When implemented in a derived class, clears the specified UIElement and allows it to be /// either re-used or released. /// /// The element to clear. - protected abstract void RecycleElementCore(ILayoutable element); + protected abstract void RecycleElementCore(Layoutable element); internal NonVirtualizingLayoutContext GetNonVirtualizingContextAdapter() => _contextAdapter ?? (_contextAdapter = new VirtualLayoutContextAdapter(this)); diff --git a/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs b/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs index e823ade6d4..7a44f034f5 100644 --- a/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs +++ b/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs @@ -20,6 +20,6 @@ namespace Avalonia.Layout public UvMeasure? Position { get; internal set; } - public ILayoutable? Element { get; internal set; } + public Layoutable? Element { get; internal set; } } } diff --git a/src/Avalonia.Base/Media/ArcSegment.cs b/src/Avalonia.Base/Media/ArcSegment.cs index 888cb896bb..b7dbd4925b 100644 --- a/src/Avalonia.Base/Media/ArcSegment.cs +++ b/src/Avalonia.Base/Media/ArcSegment.cs @@ -1,3 +1,4 @@ +using System; using System.Globalization; namespace Avalonia.Media @@ -100,6 +101,6 @@ namespace Avalonia.Media } public override string ToString() - => $"A {Size} {RotationAngle.ToString(CultureInfo.InvariantCulture)} {(IsLargeArc ? 1 : 0)} {(int)SweepDirection} {Point}"; + => FormattableString.Invariant($"A {Size} {RotationAngle} {(IsLargeArc ? 1 : 0)} {(int)SweepDirection} {Point}"); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/BezierSegment .cs b/src/Avalonia.Base/Media/BezierSegment .cs index d4330830d9..64ea2924cc 100644 --- a/src/Avalonia.Base/Media/BezierSegment .cs +++ b/src/Avalonia.Base/Media/BezierSegment .cs @@ -1,3 +1,5 @@ +using System; + namespace Avalonia.Media { public sealed class BezierSegment : PathSegment @@ -60,6 +62,6 @@ namespace Avalonia.Media } public override string ToString() - => $"C {Point1} {Point2} {Point3}"; + => FormattableString.Invariant($"C {Point1} {Point2} {Point3}"); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs index cc97d89cfc..2139516be6 100644 --- a/src/Avalonia.Base/Media/BoxShadow.cs +++ b/src/Avalonia.Base/Media/BoxShadow.cs @@ -94,22 +94,22 @@ namespace Avalonia.Media if (OffsetX != 0.0) { - sb.AppendFormat(" {0}", OffsetX.ToString()); + sb.AppendFormat(" {0}", OffsetX.ToString(CultureInfo.InvariantCulture)); } if (OffsetY != 0.0) { - sb.AppendFormat(" {0}", OffsetY.ToString()); + sb.AppendFormat(" {0}", OffsetY.ToString(CultureInfo.InvariantCulture)); } if (Blur != 0.0) { - sb.AppendFormat(" {0}", Blur.ToString()); + sb.AppendFormat(" {0}", Blur.ToString(CultureInfo.InvariantCulture)); } if (Spread != 0.0) { - sb.AppendFormat(" {0}", Spread.ToString()); + sb.AppendFormat(" {0}", Spread.ToString(CultureInfo.InvariantCulture)); } sb.AppendFormat(" {0}", Color.ToString()); diff --git a/src/Avalonia.Base/Media/IVisualBrush.cs b/src/Avalonia.Base/Media/IVisualBrush.cs index b8900bbd73..6662613ff4 100644 --- a/src/Avalonia.Base/Media/IVisualBrush.cs +++ b/src/Avalonia.Base/Media/IVisualBrush.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Paints an area with an . + /// Paints an area with an . /// [NotClientImplementable] public interface IVisualBrush : ITileBrush @@ -12,6 +12,6 @@ namespace Avalonia.Media /// /// Gets the visual to draw. /// - IVisual Visual { get; } + Visual Visual { get; } } } diff --git a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs index 94a5abd918..d56711ad68 100644 --- a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs @@ -7,7 +7,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media.Imaging { /// - /// A bitmap that holds the rendering of a . + /// A bitmap that holds the rendering of a . /// public class RenderTargetBitmap : Bitmap, IDisposable, IRenderTarget { @@ -44,7 +44,7 @@ namespace Avalonia.Media.Imaging /// Renders a visual to the . /// /// The visual to render. - public void Render(IVisual visual) => ImmediateRenderer.Render(visual, this); + public void Render(Visual visual) => ImmediateRenderer.Render(visual, this); /// /// Creates a platform-specific implementation for a . diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableTransform.cs b/src/Avalonia.Base/Media/Immutable/ImmutableTransform.cs index d5ff2b8317..4478504eca 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableTransform.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableTransform.cs @@ -3,7 +3,7 @@ namespace Avalonia.Media.Immutable { /// - /// Represents a transform on an . + /// Represents a transform on an . /// public class ImmutableTransform : ITransform { diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs index b436dcdb5e..9b443391c5 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media.Immutable { /// - /// Paints an area with an . + /// Paints an area with an . /// internal class ImmutableVisualBrush : ImmutableTileBrush, IVisualBrush { @@ -25,7 +25,7 @@ namespace Avalonia.Media.Immutable /// The tile mode. /// Controls the quality of interpolation. public ImmutableVisualBrush( - IVisual visual, + Visual visual, AlignmentX alignmentX = AlignmentX.Center, AlignmentY alignmentY = AlignmentY.Center, RelativeRect? destinationRect = null, @@ -62,6 +62,6 @@ namespace Avalonia.Media.Immutable } /// - public IVisual Visual { get; } + public Visual Visual { get; } } } diff --git a/src/Avalonia.Base/Media/LineSegment.cs b/src/Avalonia.Base/Media/LineSegment.cs index 49d9ebeb72..5729ab2c3b 100644 --- a/src/Avalonia.Base/Media/LineSegment.cs +++ b/src/Avalonia.Base/Media/LineSegment.cs @@ -1,3 +1,5 @@ +using System; + namespace Avalonia.Media { public sealed class LineSegment : PathSegment @@ -26,6 +28,6 @@ namespace Avalonia.Media } public override string ToString() - => $"L {Point}"; + => FormattableString.Invariant($"L {Point}"); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/MatrixTransform.cs b/src/Avalonia.Base/Media/MatrixTransform.cs index 4e60e1e290..c61acb730c 100644 --- a/src/Avalonia.Base/Media/MatrixTransform.cs +++ b/src/Avalonia.Base/Media/MatrixTransform.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Transforms an according to a . + /// Transforms an according to a . /// public class MatrixTransform : Transform { diff --git a/src/Avalonia.Base/Media/PathFigure.cs b/src/Avalonia.Base/Media/PathFigure.cs index caf86cb234..e1b5e8b504 100644 --- a/src/Avalonia.Base/Media/PathFigure.cs +++ b/src/Avalonia.Base/Media/PathFigure.cs @@ -126,7 +126,7 @@ namespace Avalonia.Media } public override string ToString() - => $"M {StartPoint} {string.Join(" ", _segments ?? Enumerable.Empty())}{(IsClosed ? "Z" : "")}"; + => FormattableString.Invariant($"M {StartPoint} {string.Join(" ", _segments ?? Enumerable.Empty())}{(IsClosed ? "Z" : "")}"); internal void ApplyTo(StreamGeometryContext ctx) { diff --git a/src/Avalonia.Base/Media/PathGeometry.cs b/src/Avalonia.Base/Media/PathGeometry.cs index 2c8a51c541..8292afde7e 100644 --- a/src/Avalonia.Base/Media/PathGeometry.cs +++ b/src/Avalonia.Base/Media/PathGeometry.cs @@ -133,7 +133,7 @@ namespace Avalonia.Media public override string ToString() { var figuresString = _figures is not null ? string.Join(" ", _figures) : string.Empty; - return $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{figuresString}"; + return FormattableString.Invariant($"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{figuresString}"); } } } diff --git a/src/Avalonia.Base/Media/PathMarkupParser.cs b/src/Avalonia.Base/Media/PathMarkupParser.cs index 5e808488fc..7b9fdf9330 100644 --- a/src/Avalonia.Base/Media/PathMarkupParser.cs +++ b/src/Avalonia.Base/Media/PathMarkupParser.cs @@ -527,7 +527,7 @@ namespace Avalonia.Media return span.Slice(i); } - private bool ReadBool(ref ReadOnlySpan span) + private static bool ReadBool(ref ReadOnlySpan span) { span = SkipWhitespace(span); @@ -551,7 +551,7 @@ namespace Avalonia.Media } } - private double ReadDouble(ref ReadOnlySpan span) + private static double ReadDouble(ref ReadOnlySpan span) { if (!ReadArgument(ref span, out var doubleValue)) { @@ -561,7 +561,7 @@ namespace Avalonia.Media return double.Parse(doubleValue.ToString(), CultureInfo.InvariantCulture); } - private Size ReadSize(ref ReadOnlySpan span) + private static Size ReadSize(ref ReadOnlySpan span) { var width = ReadDouble(ref span); span = ReadSeparator(span); @@ -569,7 +569,7 @@ namespace Avalonia.Media return new Size(width, height); } - private Point ReadPoint(ref ReadOnlySpan span) + private static Point ReadPoint(ref ReadOnlySpan span) { var x = ReadDouble(ref span); span = ReadSeparator(span); @@ -577,7 +577,7 @@ namespace Avalonia.Media return new Point(x, y); } - private Point ReadRelativePoint(ref ReadOnlySpan span, Point origin) + private static Point ReadRelativePoint(ref ReadOnlySpan span, Point origin) { var x = ReadDouble(ref span); span = ReadSeparator(span); @@ -585,7 +585,7 @@ namespace Avalonia.Media return new Point(origin.X + x, origin.Y + y); } - private bool ReadCommand(ref ReadOnlySpan span, out Command command, out bool relative) + private static bool ReadCommand(ref ReadOnlySpan span, out Command command, out bool relative) { span = SkipWhitespace(span); if (span.IsEmpty) diff --git a/src/Avalonia.Base/Media/QuadraticBezierSegment .cs b/src/Avalonia.Base/Media/QuadraticBezierSegment .cs index f1a0ccaaa0..9dc24e2a93 100644 --- a/src/Avalonia.Base/Media/QuadraticBezierSegment .cs +++ b/src/Avalonia.Base/Media/QuadraticBezierSegment .cs @@ -1,3 +1,5 @@ +using System; + namespace Avalonia.Media { public sealed class QuadraticBezierSegment : PathSegment @@ -44,6 +46,6 @@ namespace Avalonia.Media } public override string ToString() - => $"Q {Point1} {Point2}"; + => FormattableString.Invariant($"Q {Point1} {Point2}"); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/RotateTransform.cs b/src/Avalonia.Base/Media/RotateTransform.cs index 126bb7c274..3bd409149c 100644 --- a/src/Avalonia.Base/Media/RotateTransform.cs +++ b/src/Avalonia.Base/Media/RotateTransform.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Rotates an . + /// Rotates an . /// public class RotateTransform : Transform { diff --git a/src/Avalonia.Base/Media/ScaleTransform.cs b/src/Avalonia.Base/Media/ScaleTransform.cs index 259b23cbd2..d4c1a7f993 100644 --- a/src/Avalonia.Base/Media/ScaleTransform.cs +++ b/src/Avalonia.Base/Media/ScaleTransform.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Scale an . + /// Scale an . /// public class ScaleTransform : Transform { diff --git a/src/Avalonia.Base/Media/SkewTransform.cs b/src/Avalonia.Base/Media/SkewTransform.cs index a96710e331..066f5371c3 100644 --- a/src/Avalonia.Base/Media/SkewTransform.cs +++ b/src/Avalonia.Base/Media/SkewTransform.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Skews an . + /// Skews an . /// public class SkewTransform : Transform { diff --git a/src/Avalonia.Base/Media/Transform.cs b/src/Avalonia.Base/Media/Transform.cs index 023a8b9cdd..85393ab189 100644 --- a/src/Avalonia.Base/Media/Transform.cs +++ b/src/Avalonia.Base/Media/Transform.cs @@ -7,7 +7,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Represents a transform on an . + /// Represents a transform on an . /// public abstract class Transform : Animatable, IMutableTransform { diff --git a/src/Avalonia.Base/Media/TranslateTransform.cs b/src/Avalonia.Base/Media/TranslateTransform.cs index d6d6809f3d..0f910f3600 100644 --- a/src/Avalonia.Base/Media/TranslateTransform.cs +++ b/src/Avalonia.Base/Media/TranslateTransform.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Translates (moves) an . + /// Translates (moves) an . /// public class TranslateTransform : Transform { diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index c665507652..8dce79cf11 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -4,15 +4,15 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Paints an area with an . + /// Paints an area with an . /// public class VisualBrush : TileBrush, IVisualBrush { /// /// Defines the property. /// - public static readonly StyledProperty VisualProperty = - AvaloniaProperty.Register(nameof(Visual)); + public static readonly StyledProperty VisualProperty = + AvaloniaProperty.Register(nameof(Visual)); static VisualBrush() { @@ -30,7 +30,7 @@ namespace Avalonia.Media /// Initializes a new instance of the class. /// /// The visual to draw. - public VisualBrush(IVisual visual) + public VisualBrush(Visual visual) { Visual = visual; } @@ -38,7 +38,7 @@ namespace Avalonia.Media /// /// Gets or sets the visual to draw. /// - public IVisual Visual + public Visual Visual { get { return GetValue(VisualProperty); } set { SetValue(VisualProperty, value); } diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs index a74da2a178..659cfb75df 100644 --- a/src/Avalonia.Base/Platform/AssetLoader.cs +++ b/src/Avalonia.Base/Platform/AssetLoader.cs @@ -3,16 +3,22 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +#if !BUILDTASK using Avalonia.Platform.Internal; using Avalonia.Utilities; +#endif namespace Avalonia.Platform { /// /// Loads assets compiled into the application binary. /// - public class AssetLoader : IAssetLoader + public class AssetLoader +#if !BUILDTASK + : IAssetLoader +#endif { +#if !BUILDTASK private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver(); private AssemblyDescriptor? _defaultResmAssembly; @@ -181,13 +187,13 @@ namespace Avalonia.Platform throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri)); } - private (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri) + private static (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri) { var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority); return (asm, uri.GetUnescapeAbsolutePath()); } - private IAssemblyDescriptor? GetAssembly(Uri? uri) + private static IAssemblyDescriptor? GetAssembly(Uri? uri) { if (uri != null) { @@ -206,7 +212,8 @@ namespace Avalonia.Platform return null; } - +#endif + public static void RegisterResUriParsers() { if (!UriParser.IsKnownScheme("avares")) diff --git a/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs index 3d3b1a3e4d..5d60b44bef 100644 --- a/src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs +++ b/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 : IObserver, IDisposable { diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 1d8f6b8408..93fffb3755 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -262,7 +262,7 @@ namespace Avalonia.PropertyStore private class UncommonFields { - public Func? _coerce; + public Func? _coerce; public T? _uncoercedValue; public T? _uncoercedBaseValue; } diff --git a/src/Avalonia.Base/PropertyStore/FramePriority.cs b/src/Avalonia.Base/PropertyStore/FramePriority.cs new file mode 100644 index 0000000000..950a8375f2 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/FramePriority.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal enum FramePriority : sbyte + { + Animation, + AnimationTemplatedParentTheme, + AnimationTheme, + StyleTrigger, + StyleTriggerTemplatedParentTheme, + StyleTriggerTheme, + Template, + TemplateTemplatedParentTheme, + TemplateTheme, + Style, + StyleTemplatedParentTheme, + StyleTheme, + } + + internal static class FramePriorityExtensions + { + public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style) + { + Debug.Assert(priority != BindingPriority.LocalValue); + var p = (int)(priority > 0 ? priority : priority + 1); + return (FramePriority)(p * 3 + (int)type); + } + + public static bool IsType(this FramePriority priority, FrameType type) + { + return (FrameType)((int)priority % 3) == type; + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs index 1d886e7501..50d5333b9f 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -7,11 +7,11 @@ namespace Avalonia.PropertyStore /// Holds values in a set by one of the SetValue or AddBinding /// overloads with non-LocalValue priority. /// - internal class ImmediateValueFrame : ValueFrame + internal sealed class ImmediateValueFrame : ValueFrame { public ImmediateValueFrame(BindingPriority priority) + : base(priority, FrameType.Style) { - Priority = priority; } public TypedBindingEntry AddBinding( diff --git a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs index acaecc0d52..5c5591dcb5 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs +++ b/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 ConvertAndValidate( object? value, Type targetType, @@ -23,18 +24,7 @@ namespace Avalonia.PropertyStore return v; } - public static bool TryConvertAndValidate( - AvaloniaProperty property, - object? value, - out object? result) - { - if (TypeUtilities.TryConvertImplicit(property.PropertyType, value, out result)) - return ((IStyledPropertyAccessor)property).ValidateValue(result); - - result = default; - return false; - } - + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] public static bool TryConvertAndValidate( StyledPropertyBase property, object? value, diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs index 5ada4b3c84..7a9d1bb13a 100644 --- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs @@ -1,13 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.Utilities; -using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { + internal enum FrameType + { + Style, + TemplatedParentTheme, + Theme, + } + internal abstract class ValueFrame { private List? _entries; @@ -15,11 +20,18 @@ namespace Avalonia.PropertyStore private ValueStore? _owner; private bool _isShared; + protected ValueFrame(BindingPriority priority, FrameType type) + { + Priority = priority; + FramePriority = priority.ToFramePriority(type); + } + public int EntryCount => _index.Count; public bool IsActive => GetIsActive(out _); public ValueStore? Owner => !_isShared ? _owner : throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame"); - public BindingPriority Priority { get; protected set; } + public BindingPriority Priority { get; } + public FramePriority FramePriority { get; } public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property); diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 8790991182..92e5288255 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Avalonia.Data; using Avalonia.Diagnostics; -using Avalonia.Logging; +using Avalonia.Styling; using Avalonia.Utilities; +using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { @@ -580,6 +582,53 @@ namespace Avalonia.PropertyStore return false; } + public void RemoveFrames(FrameType type) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame is not ImmediateValueFrame && frame.FramePriority.IsType(type)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + + + public void RemoveFrames(IReadOnlyList styles) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame is StyleInstance style && styles.Contains(style.Source)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property) { object? value; @@ -610,10 +659,9 @@ namespace Avalonia.PropertyStore private int InsertFrame(ValueFrame frame) { - // Uncomment this line when #8549 is fixed. - //Debug.Assert(!_frames.Contains(frame)); + Debug.Assert(!_frames.Contains(frame)); - var index = BinarySearchFrame(frame.Priority); + var index = BinarySearchFrame(frame.FramePriority); _frames.Insert(index, frame); ++_frameGeneration; frame.SetOwner(this); @@ -627,7 +675,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(priority != BindingPriority.LocalValue); - var index = BinarySearchFrame(priority); + var index = BinarySearchFrame(priority.ToFramePriority()); if (index > 0 && _frames[index - 1] is ImmediateValueFrame f && f.Priority == priority && @@ -915,7 +963,7 @@ namespace Avalonia.PropertyStore } } - private int BinarySearchFrame(BindingPriority priority) + private int BinarySearchFrame(FramePriority priority) { var lo = 0; var hi = _frames.Count - 1; @@ -924,7 +972,7 @@ namespace Avalonia.PropertyStore while (lo <= hi) { var i = lo + ((hi - lo) >> 1); - var order = priority - _frames[i].Priority; + var order = priority - _frames[i].FramePriority; if (order <= 0) { diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs index 9f038214c4..0789684eff 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs @@ -6,15 +6,15 @@ namespace Avalonia.Reactive { internal class AvaloniaPropertyBindingObservable : LightweightObservableBase>, IDescription { - private readonly WeakReference _target; + private readonly WeakReference _target; private readonly AvaloniaProperty _property; private BindingValue _value = BindingValue.Unset; public AvaloniaPropertyBindingObservable( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property) { - _target = new WeakReference(target); + _target = new WeakReference(target); _property = property; } diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs index dc29ddbdff..fa876a8906 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs @@ -6,14 +6,14 @@ namespace Avalonia.Reactive LightweightObservableBase, IDescription { - private readonly WeakReference _target; + private readonly WeakReference _target; private readonly AvaloniaProperty _property; public AvaloniaPropertyChangedObservable( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property) { - _target = new WeakReference(target); + _target = new WeakReference(target); _property = property; } diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs index cafce58c9c..a4fa587a50 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs @@ -6,15 +6,15 @@ namespace Avalonia.Reactive { internal class AvaloniaPropertyObservable : LightweightObservableBase, IDescription { - private readonly WeakReference _target; + private readonly WeakReference _target; private readonly AvaloniaProperty _property; private Optional _value; public AvaloniaPropertyObservable( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property) { - _target = new WeakReference(target); + _target = new WeakReference(target); _property = property; } @@ -49,31 +49,23 @@ namespace Avalonia.Reactive { if (e.Property == _property) { - if (e.Sender is AvaloniaObject ao) - { - T newValue; - - if (e is AvaloniaPropertyChangedEventArgs typed) - { - newValue = AvaloniaObjectExtensions.GetValue(ao, typed.Property); - } - else - { - newValue = (T)e.Sender.GetValue(e.Property)!; - } + T newValue; - if (!_value.HasValue || - !EqualityComparer.Default.Equals(newValue, _value.Value)) - { - _value = newValue; - PublishNext(_value.Value!); - } + if (e is AvaloniaPropertyChangedEventArgs typed) + { + newValue = AvaloniaObjectExtensions.GetValue(e.Sender, typed.Property); } else { - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + newValue = (T)e.Sender.GetValue(e.Property)!; } + if (!_value.HasValue || + !EqualityComparer.Default.Equals(newValue, _value.Value)) + { + _value = newValue; + PublishNext(_value.Value!); + } } } } diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index 7930228d99..a91b089a33 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -243,7 +243,7 @@ namespace Avalonia } /// - /// Determines whether a point in in the bounds of the rectangle. + /// Determines whether a point is in the bounds of the rectangle. /// /// The point. /// true if the point is in the bounds of the rectangle; otherwise false. diff --git a/src/Avalonia.Base/RelativePoint.cs b/src/Avalonia.Base/RelativePoint.cs index e1fd0093b6..71c6a5cc15 100644 --- a/src/Avalonia.Base/RelativePoint.cs +++ b/src/Avalonia.Base/RelativePoint.cs @@ -199,7 +199,7 @@ namespace Avalonia { return _unit == RelativeUnit.Absolute ? _point.ToString() : - string.Format(CultureInfo.InvariantCulture, "{0}%, {1}%", _point.X * 100, _point.Y * 100); + string.Format(CultureInfo.InvariantCulture, "{0}%, {1}%", _point.X * 100, _point.Y * 100); } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs index e20a4a9ad8..e9f58be095 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs @@ -87,7 +87,7 @@ namespace Avalonia.Rendering.Composition.Animations if (elapsed < _delayTime) { if (_delayBehavior == AnimationDelayBehavior.SetInitialValueBeforeDelay) - return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[0])); + return ExpressionVariant.Create(KeyFrameAnimationInstance.GetKeyFrame(ref ctx, _keyFrames[0])); return currentValue; } @@ -95,7 +95,7 @@ namespace Avalonia.Rendering.Composition.Animations var iterationNumber = elapsed.Ticks / _duration.Ticks; if (_iterationBehavior == AnimationIterationBehavior.Count && iterationNumber >= _iterationCount) - return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[_keyFrames.Length - 1])); + return ExpressionVariant.Create(KeyFrameAnimationInstance.GetKeyFrame(ref ctx, _keyFrames[_keyFrames.Length - 1])); var evenIterationNumber = iterationNumber % 2 == 0; @@ -124,12 +124,20 @@ namespace Avalonia.Rendering.Composition.Animations { // this is the last frame if (c == _keyFrames.Length - 1) - return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf)); + return ExpressionVariant.Create(KeyFrameAnimationInstance.GetKeyFrame(ref ctx, kf)); left = kf; right = _keyFrames[c + 1]; + } + else if (c == 0) + { + // The current progress is before the first frame, we implicitly use the starting value + // as the first frame in this case + right = _keyFrames[c]; break; } + else + break; } var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key))); @@ -139,13 +147,13 @@ namespace Avalonia.Rendering.Composition.Animations return currentValue; return ExpressionVariant.Create(_interpolator.Interpolate( - GetKeyFrame(ref ctx, left), - GetKeyFrame(ref ctx, right), + KeyFrameAnimationInstance.GetKeyFrame(ref ctx, left), + KeyFrameAnimationInstance.GetKeyFrame(ref ctx, right), easedKeyProgress )); } - T GetKeyFrame(ref ExpressionEvaluationContext ctx, ServerKeyFrame f) + static T GetKeyFrame(ref ExpressionEvaluationContext ctx, ServerKeyFrame f) { if (f.Expression != null) return f.Expression.Evaluate(ref ctx).CastOrDefault(); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 98a6a3600e..ea8a408e6f 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -45,7 +45,7 @@ public class CompositingRenderer : IRendererWithCompositor _compositor = compositor; _recordingContext = new DrawingContext(_recorder); CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget); - CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); + CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor); _update = Update; } @@ -75,7 +75,7 @@ public class CompositingRenderer : IRendererWithCompositor } /// - public void AddDirty(IVisual visual) + public void AddDirty(Visual visual) { if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); @@ -84,7 +84,7 @@ public class CompositingRenderer : IRendererWithCompositor } /// - public IEnumerable HitTest(Point p, IVisual root, Func? filter) + public IEnumerable HitTest(Point p, Visual root, Func? filter) { Func? f = null; if (filter != null) @@ -109,14 +109,14 @@ public class CompositingRenderer : IRendererWithCompositor } /// - public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) + public Visual? HitTestFirst(Point p, Visual root, Func? filter) { // TODO: Optimize return HitTest(p, root, filter).FirstOrDefault(); } /// - public void RecalculateChildren(IVisual visual) + public void RecalculateChildren(Visual visual) { if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); @@ -124,15 +124,15 @@ public class CompositingRenderer : IRendererWithCompositor QueueUpdate(); } - private void SyncChildren(Visual v) + private static void SyncChildren(Visual v) { //TODO: Optimize by moving that logic to Visual itself if(v.CompositionVisual == null) return; var compositionChildren = v.CompositionVisual.Children; - var visualChildren = (AvaloniaList)v.GetVisualChildren(); + var visualChildren = (AvaloniaList)v.GetVisualChildren(); - PooledList<(IVisual visual, int index)>? sortedChildren = null; + PooledList<(Visual visual, int index)>? sortedChildren = null; if (v.HasNonUniformZIndexChildren && visualChildren.Count > 1) { sortedChildren = new (visualChildren.Count); @@ -146,8 +146,18 @@ public class CompositingRenderer : IRendererWithCompositor return result == 0 ? lhs.index.CompareTo(rhs.index) : result; }); } - - if (compositionChildren.Count == visualChildren.Count) + + var childVisual = v.ChildCompositionVisual; + + // Check if the current visual somehow got migrated to another compositor + if (childVisual != null && childVisual.Compositor != v.CompositionVisual.Compositor) + childVisual = null; + + var expectedCount = visualChildren.Count; + if (childVisual != null) + expectedCount++; + + if (compositionChildren.Count == expectedCount) { bool mismatch = false; if (sortedChildren != null) @@ -167,6 +177,9 @@ public class CompositingRenderer : IRendererWithCompositor break; } + if (childVisual != null && + !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual)) + mismatch = true; if (!mismatch) { @@ -193,6 +206,9 @@ public class CompositingRenderer : IRendererWithCompositor if (compositionChild != null) compositionChildren.Add(compositionChild); } + + if (childVisual != null) + compositionChildren.Add(childVisual); } private void UpdateCore() diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index d8a608651b..0c24d6cd44 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -62,7 +62,7 @@ namespace Avalonia.Rendering.Composition return point * m; } - bool TryGetInvertedTransform(CompositionVisual visual, out Matrix matrix) + static bool TryGetInvertedTransform(CompositionVisual visual, out Matrix matrix) { var m = visual.TryGetServerGlobalTransform(); if (m == null) @@ -75,7 +75,7 @@ namespace Avalonia.Rendering.Composition return m33.TryInvert(out matrix); } - bool TryTransformTo(CompositionVisual visual, Point globalPoint, out Point v) + static bool TryTransformTo(CompositionVisual visual, Point globalPoint, out Point v) { v = default; if (TryGetInvertedTransform(visual, out var m)) diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs index 1a13d23acd..00fa7b3315 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs @@ -29,4 +29,7 @@ public partial class Compositor public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this); public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this); + + public CompositionSolidColorVisual CreateSolidColorVisual() => + new(this, new ServerCompositionSolidColorVisual(Server)); } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index 30b57883fc..aae1fcb90e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -368,7 +368,7 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW : null; } - private IDisposable? CreateChildScene(IBrush? brush) + private static IDisposable? CreateChildScene(IBrush? brush) { if (brush is VisualBrush visualBrush) { diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs index 5bd8e4a4d3..b01321edd8 100644 --- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs +++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs @@ -1,5 +1,8 @@ // Special license applies License.md +using System; +using Avalonia.VisualTree; + namespace Avalonia.Rendering.Composition; /// @@ -13,4 +16,22 @@ public static class ElementComposition /// /// public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; + + /// + /// Sets a custom as the last child of the element’s visual tree. + /// + public static void SetElementChildVisual(Visual visual, CompositionVisual? compositionVisual) + { + if (compositionVisual != null && visual.CompositionVisual != null && + compositionVisual.Compositor != visual.CompositionVisual.Compositor) + throw new InvalidOperationException("Composition visuals belong to different compositor instances"); + + visual.ChildCompositionVisual = compositionVisual; + visual.GetVisualRoot()?.Renderer.RecalculateChildren(visual); + } + + /// + /// Retrieves a object previously set by a call to . + /// + public static CompositionVisual? GetElementChildVisual(Visual visual) => visual.ChildCompositionVisual; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index 06fb526736..2019ad6faa 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -52,7 +52,7 @@ internal class FpsCounter _lastFpsUpdate = now; } - var fpsLine = $"Frame #{_totalFrames:00000000} FPS: {_fps:000} " + aux; + var fpsLine = FormattableString.Invariant($"Frame #{_totalFrames:00000000} FPS: {_fps:000} ") + aux; double width = 0; double height = 0; foreach (var ch in fpsLine) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs new file mode 100644 index 0000000000..79abd7ee17 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs @@ -0,0 +1,11 @@ +using Avalonia.Media.Immutable; + +namespace Avalonia.Rendering.Composition.Server; + +internal partial class ServerCompositionSolidColorVisual +{ + protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) + { + canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y)); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 5c1ac0312c..f5a46506a3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -156,7 +156,7 @@ namespace Avalonia.Rendering.Composition.Server (Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) * Compositor.BatchObjectPool.ArraySize * IntPtr.Size), false); - _fpsCounter.RenderFps(targetContext, $"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}"); + _fpsCounter.RenderFps(targetContext, FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}")); } RenderedVisuals = 0; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index b742f1d44a..d724e14298 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -48,7 +48,7 @@ namespace Avalonia.Rendering.Composition.Server if (Opacity != 1) canvas.PushOpacity(Opacity); var boundsRect = new Rect(new Size(Size.X, Size.Y)); - if(ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); if (Clip != null) canvas.PushGeometryClip(Clip); @@ -65,11 +65,13 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopOpacityMask(); if (Clip != null) canvas.PopGeometryClip(); - if (ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); if(Opacity != 1) canvas.PopOpacity(); } + + protected virtual bool HandlesClipToBounds => false; private ReadbackData _readback0, _readback1, _readback2; diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs index 4236763e3b..c3bf861c47 100644 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Base/Rendering/DeferredRenderer.cs @@ -22,14 +22,14 @@ namespace Avalonia.Rendering { private readonly IDispatcher? _dispatcher; private readonly IRenderLoop? _renderLoop; - private readonly IVisual _root; + private readonly Visual _root; private readonly ISceneBuilder _sceneBuilder; private bool _running; private bool _disposed; private volatile IRef? _scene; private DirtyVisuals? _dirty; - private HashSet? _recalculateChildren; + private HashSet? _recalculateChildren; private IRef? _overlay; private int _lastSceneId = -1; private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); @@ -56,7 +56,7 @@ namespace Avalonia.Rendering IDeferredRendererLock? rendererLock = null) : base(true) { _dispatcher = dispatcher ?? Dispatcher.UIThread; - _root = root ?? throw new ArgumentNullException(nameof(root)); + _root = root as Visual ?? throw new ArgumentNullException(nameof(root)); _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); _renderLoop = renderLoop; @@ -74,7 +74,7 @@ namespace Avalonia.Rendering /// This constructor is intended to be used for unit testing. /// public DeferredRenderer( - IVisual root, + Visual root, IRenderTarget renderTarget, ISceneBuilder? sceneBuilder = null) : base(true) { @@ -116,7 +116,7 @@ namespace Avalonia.Rendering internal IRenderTarget? RenderTarget { get; private set; } /// - public void AddDirty(IVisual visual) + public void AddDirty(Visual visual) { _dirty?.Add(visual); } @@ -142,7 +142,7 @@ namespace Avalonia.Rendering DisposeRenderTarget(); } - public void RecalculateChildren(IVisual visual) => _recalculateChildren?.Add(visual); + public void RecalculateChildren(Visual visual) => _recalculateChildren?.Add(visual); void DisposeRenderTarget() { @@ -163,17 +163,17 @@ namespace Avalonia.Rendering } /// - public IEnumerable HitTest(Point p, IVisual root, Func? filter) + public IEnumerable HitTest(Point p, Visual root, Func? filter) { EnsureCanHitTest(); //It's safe to access _scene here without a lock since //it's only changed from UI thread which we are currently on - return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty(); + return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty(); } /// - public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) + public Visual? HitTestFirst(Point p, Visual root, Func? filter) { EnsureCanHitTest(); @@ -272,7 +272,7 @@ namespace Avalonia.Rendering } } - Scene? TryGetChildScene(IRef? op) => (op?.Item as BrushDrawOperation)?.Aux as Scene; + static Scene? TryGetChildScene(IRef? op) => (op?.Item as BrushDrawOperation)?.Aux as Scene; /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) @@ -414,7 +414,7 @@ namespace Avalonia.Rendering } - private void Render(IDrawingContextImpl context, VisualNode node, IVisual? layer, Rect clipBounds) + private void Render(IDrawingContextImpl context, VisualNode node, Visual? layer, Rect clipBounds) { if (layer == null || node.LayerRoot == layer) { @@ -643,7 +643,7 @@ namespace Avalonia.Rendering if (_dirty == null) { _dirty = new DirtyVisuals(); - _recalculateChildren = new HashSet(); + _recalculateChildren = new HashSet(); _sceneBuilder.UpdateAll(scene); } else @@ -725,7 +725,7 @@ namespace Avalonia.Rendering foreach (var layer in Layers) { - var fileName = Path.Combine(DebugFramesPath ?? string.Empty, $"frame-{id}-layer-{index++}.png"); + var fileName = Path.Combine(DebugFramesPath ?? string.Empty, FormattableString.Invariant($"frame-{id}-layer-{index++}.png")); layer.Bitmap.Item.Save(fileName); } } diff --git a/src/Avalonia.Base/Rendering/DirtyVisuals.cs b/src/Avalonia.Base/Rendering/DirtyVisuals.cs index 999b12e810..dfadbdf614 100644 --- a/src/Avalonia.Base/Rendering/DirtyVisuals.cs +++ b/src/Avalonia.Base/Rendering/DirtyVisuals.cs @@ -13,10 +13,10 @@ namespace Avalonia.Rendering /// visual. TODO: We probably want to put an upper limit on the number of visuals that can be /// stored and if we reach that limit, assume all visuals are dirty. /// - internal class DirtyVisuals : IEnumerable + internal class DirtyVisuals : IEnumerable { - private SortedDictionary> _inner = new SortedDictionary>(); - private Dictionary _index = new Dictionary(); + private SortedDictionary> _inner = new SortedDictionary>(); + private Dictionary _index = new Dictionary(); private int _enumerating; /// @@ -28,14 +28,14 @@ namespace Avalonia.Rendering /// Adds a visual to the dirty list. /// /// The dirty visual. - public void Add(IVisual visual) + public void Add(Visual visual) { if (_enumerating > 0) { throw new InvalidOperationException("Visual was invalidated during a render pass"); } - var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot); + var distance = visual.CalculateDistanceFromAncestor((Visual?)visual.GetVisualRoot()); if (_index.TryGetValue(visual, out var existingDistance)) { @@ -50,7 +50,7 @@ namespace Avalonia.Rendering if (!_inner.TryGetValue(distance, out var list)) { - list = new List(); + list = new List(); _inner.Add(distance, list); } @@ -76,7 +76,7 @@ namespace Avalonia.Rendering /// Gets the dirty visuals, in ascending order of distance to their root. /// /// A collection of visuals. - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { _enumerating++; try diff --git a/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs index 354a344ffe..fae595413f 100644 --- a/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs +++ b/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs @@ -24,10 +24,10 @@ namespace Avalonia.Rendering public static class CustomSimpleHitTestExtensions { - public static bool HitTestCustom(this IVisual visual, Point point) + public static bool HitTestCustom(this Visual visual, Point point) => (visual as ICustomSimpleHitTest)?.HitTest(point) ?? visual.TransformedBounds?.Contains(point) == true; - public static bool HitTestCustom(this IEnumerable children, Point point) + public static bool HitTestCustom(this IEnumerable children, Point point) => children.Any(ctrl => ctrl.HitTestCustom(point)); } } diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index 1aa44158b2..aa06ce7bed 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -8,7 +8,7 @@ namespace Avalonia.Rendering /// Represents the root of a renderable tree. /// [NotClientImplementable] - public interface IRenderRoot : IVisual + public interface IRenderRoot { /// /// Gets the client size of the window. diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs index 8d6aabf440..cb36aad7cc 100644 --- a/src/Avalonia.Base/Rendering/IRenderer.cs +++ b/src/Avalonia.Base/Rendering/IRenderer.cs @@ -34,7 +34,7 @@ namespace Avalonia.Rendering /// Mark a visual as dirty and needing re-rendering. /// /// The visual. - void AddDirty(IVisual visual); + void AddDirty(Visual visual); /// /// Hit tests a location to find the visuals at the specified point. @@ -46,7 +46,7 @@ namespace Avalonia.Rendering /// children will be excluded from the results. /// /// The visuals at the specified point, topmost first. - IEnumerable HitTest(Point p, IVisual root, Func filter); + IEnumerable HitTest(Point p, Visual root, Func filter); /// /// Hit tests a location to find first visual at the specified point. @@ -58,13 +58,13 @@ namespace Avalonia.Rendering /// children will be excluded from the results. /// /// The visual at the specified point, topmost first. - IVisual? HitTestFirst(Point p, IVisual root, Func filter); + Visual? HitTestFirst(Point p, Visual root, Func filter); /// /// Informs the renderer that the z-ordering of a visual's children has changed. /// /// The visual. - void RecalculateChildren(IVisual visual); + void RecalculateChildren(Visual visual); /// /// Called when a resize notification is received by the control being rendered. diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 79ef52586d..9e1582ed43 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -18,7 +18,7 @@ namespace Avalonia.Rendering /// public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer { - private readonly IVisual _root; + private readonly Visual _root; private readonly IRenderRoot? _renderRoot; private bool _updateTransformedBounds = true; private IRenderTarget? _renderTarget; @@ -27,13 +27,13 @@ namespace Avalonia.Rendering /// Initializes a new instance of the class. /// /// The control to render. - public ImmediateRenderer(IVisual root) + public ImmediateRenderer(Visual root) { _root = root ?? throw new ArgumentNullException(nameof(root)); _renderRoot = root as IRenderRoot; } - private ImmediateRenderer(IVisual root, bool updateTransformedBounds) + private ImmediateRenderer(Visual root, bool updateTransformedBounds) { _root = root ?? throw new ArgumentNullException(nameof(root)); _renderRoot = root as IRenderRoot; @@ -102,7 +102,7 @@ namespace Avalonia.Rendering /// /// The visual. /// The render target. - public static void Render(IVisual visual, IRenderTarget target) + public static void Render(Visual visual, IRenderTarget target) { using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false)) using (var context = new DrawingContext(target.CreateDrawingContext(renderer))) @@ -116,7 +116,7 @@ namespace Avalonia.Rendering /// /// The visual. /// The drawing context. - public static void Render(IVisual visual, DrawingContext context) + public static void Render(Visual visual, DrawingContext context) { using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false)) { @@ -125,7 +125,7 @@ namespace Avalonia.Rendering } /// - public void AddDirty(IVisual visual) + public void AddDirty(Visual visual) { if (visual.Bounds != Rect.Empty) { @@ -162,18 +162,18 @@ namespace Avalonia.Rendering } /// - public IEnumerable HitTest(Point p, IVisual root, Func filter) + public IEnumerable HitTest(Point p, Visual root, Func filter) { return HitTest(root, p, filter); } - public IVisual? HitTestFirst(Point p, IVisual root, Func filter) + public Visual? HitTestFirst(Point p, Visual root, Func filter) { return HitTest(root, p, filter).FirstOrDefault(); } /// - public void RecalculateChildren(IVisual visual) => AddDirty(visual); + public void RecalculateChildren(Visual visual) => AddDirty(visual); /// public void Start() @@ -199,21 +199,21 @@ namespace Avalonia.Rendering Render(new DrawingContext(context), visual, visual.Bounds); } - internal static void Render(IVisual visual, DrawingContext context, bool updateTransformedBounds) + internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds) { using var renderer = new ImmediateRenderer(visual, updateTransformedBounds); renderer.Render(context, visual, visual.Bounds); } - private static void ClearTransformedBounds(IVisual visual) + private static void ClearTransformedBounds(Visual visual) { foreach (var e in visual.GetSelfAndVisualDescendants()) { - visual.TransformedBounds = null; + visual.SetTransformedBounds(null); } } - private static Rect GetTransformedBounds(IVisual visual) + private static Rect GetTransformedBounds(Visual visual) { if (visual.RenderTransform == null) { @@ -228,10 +228,10 @@ namespace Avalonia.Rendering } } - private static IEnumerable HitTest( - IVisual visual, + private static IEnumerable HitTest( + Visual visual, Point p, - Func? filter) + Func? filter) { _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -266,7 +266,7 @@ namespace Avalonia.Rendering } } - private void Render(DrawingContext context, IVisual visual, Rect clipRect) + private void Render(DrawingContext context, Visual visual, Rect clipRect) { var opacity = visual.Opacity; var clipToBounds = visual.ClipToBounds; @@ -329,11 +329,11 @@ namespace Avalonia.Rendering #pragma warning restore 0618 if (_updateTransformedBounds) - visual.TransformedBounds = transformed; + visual.SetTransformedBounds(transformed); var childrenEnumerable = visual.HasNonUniformZIndexChildren ? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance) - : (IEnumerable)visual.VisualChildren; + : (IEnumerable)visual.VisualChildren; foreach (var child in childrenEnumerable) { diff --git a/src/Avalonia.Base/Rendering/RenderLayer.cs b/src/Avalonia.Base/Rendering/RenderLayer.cs index 768e832fe5..d1e3fcafb1 100644 --- a/src/Avalonia.Base/Rendering/RenderLayer.cs +++ b/src/Avalonia.Base/Rendering/RenderLayer.cs @@ -11,7 +11,7 @@ namespace Avalonia.Rendering IDrawingContextImpl drawingContext, Size size, double scaling, - IVisual layerRoot) + Visual layerRoot) { Bitmap = RefCountable.Create(drawingContext.CreateLayer(size)); Size = size; @@ -24,7 +24,7 @@ namespace Avalonia.Rendering public bool IsEmpty { get; set; } public double Scaling { get; private set; } public Size Size { get; private set; } - public IVisual LayerRoot { get; } + public Visual LayerRoot { get; } public void RecreateBitmap(IDrawingContextImpl drawingContext, Size size, double scaling) { diff --git a/src/Avalonia.Base/Rendering/RenderLayers.cs b/src/Avalonia.Base/Rendering/RenderLayers.cs index 7583886835..eff81e6bbf 100644 --- a/src/Avalonia.Base/Rendering/RenderLayers.cs +++ b/src/Avalonia.Base/Rendering/RenderLayers.cs @@ -3,17 +3,16 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; -using Avalonia.VisualTree; namespace Avalonia.Rendering { public class RenderLayers : IEnumerable { private readonly List _inner = new List(); - private readonly Dictionary _index = new Dictionary(); + private readonly Dictionary _index = new Dictionary(); public int Count => _inner.Count; - public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; + public RenderLayer this[Visual layerRoot] => _index[layerRoot]; public void Update(Scene scene, IDrawingContextImpl context) { @@ -59,7 +58,7 @@ namespace Avalonia.Rendering _inner.Clear(); } - public bool TryGetValue(IVisual layerRoot, [NotNullWhen(true)] out RenderLayer? value) + public bool TryGetValue(Visual layerRoot, [NotNullWhen(true)] out RenderLayer? value) { return _index.TryGetValue(layerRoot, out value); } diff --git a/src/Avalonia.Base/Rendering/RendererBase.cs b/src/Avalonia.Base/Rendering/RendererBase.cs index 90ba60c42a..f0ddbd3626 100644 --- a/src/Avalonia.Base/Rendering/RendererBase.cs +++ b/src/Avalonia.Base/Rendering/RendererBase.cs @@ -36,7 +36,7 @@ namespace Avalonia.Rendering _lastFpsUpdate = now; } - var text = layerCount.HasValue ? $"Layers: {layerCount} FPS: {_fps:000}" : $"FPS: {_fps:000}"; + var text = layerCount.HasValue ? FormattableString.Invariant($"Layers: {layerCount} FPS: {_fps:000}") : FormattableString.Invariant($"FPS: {_fps:000}"); var formattedText = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, s_fontSize, Brushes.White); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs index dd993fbb8c..f469fdbfe8 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs @@ -19,6 +19,6 @@ namespace Avalonia.Rendering.SceneGraph /// The scene. /// The visual to update. /// True if changes were made, otherwise false. - bool Update(Scene scene, IVisual visual); + bool Update(Scene scene, Visual visual); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs index e042236346..59a032748d 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs @@ -2,19 +2,18 @@ using System.Collections.Generic; using Avalonia.Platform; using Avalonia.Utilities; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { /// - /// Represents a node in the low-level scene graph representing an . + /// Represents a node in the low-level scene graph representing a . /// public interface IVisualNode : IDisposable { /// /// Gets the visual to which the node relates. /// - IVisual Visual { get; } + Visual Visual { get; } /// /// Gets the parent scene graph node. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs index c436299018..2da8923497 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs @@ -13,24 +13,24 @@ namespace Avalonia.Rendering.SceneGraph /// public class Scene : IDisposable { - private readonly Dictionary _index; + private readonly Dictionary _index; private readonly TaskCompletionSource _rendered = new TaskCompletionSource(); /// /// Initializes a new instance of the class. /// /// The root visual to draw. - public Scene(IVisual rootVisual) + public Scene(Visual rootVisual) : this( new VisualNode(rootVisual, null), - new Dictionary(), + new Dictionary(), new SceneLayers(rootVisual), 0) { _index.Add(rootVisual, Root); } - private Scene(VisualNode root, Dictionary index, SceneLayers layers, int generation) + private Scene(VisualNode root, Dictionary index, SceneLayers layers, int generation) { _ = root ?? throw new ArgumentNullException(nameof(root)); @@ -87,7 +87,7 @@ namespace Avalonia.Rendering.SceneGraph /// The cloned scene. public Scene CloneScene() { - var index = new Dictionary(_index.Count); + var index = new Dictionary(_index.Count); var root = Clone((VisualNode)Root, null, index); var result = new Scene(root, index, Layers.Clone(), Generation + 1) @@ -115,7 +115,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The node representing the visual or null if it could not be found. /// - public IVisualNode? FindNode(IVisual visual) + public IVisualNode? FindNode(Visual visual) { _index.TryGetValue(visual, out var node); return node; @@ -128,10 +128,10 @@ namespace Avalonia.Rendering.SceneGraph /// The root of the subtree to search. /// A filter. May be null. /// The visuals at the specified point. - public IEnumerable HitTest(Point p, IVisual root, Func? filter) + public IEnumerable HitTest(Point p, Visual root, Func? filter) { var node = FindNode(root); - return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty(); + return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty(); } /// @@ -141,7 +141,7 @@ namespace Avalonia.Rendering.SceneGraph /// The root of the subtree to search. /// A filter. May be null. /// The visual at the specified point. - public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) + public Visual? HitTestFirst(Point p, Visual root, Func? filter) { var node = FindNode(root); return (node != null) ? HitTestFirst(node, p, filter) : null; @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph node.Dispose(); } - private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary index) + private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary index) { var result = source.Clone(parent); @@ -184,7 +184,7 @@ namespace Avalonia.Rendering.SceneGraph return result; } - private IVisual HitTestFirst(IVisualNode root, Point p, Func? filter) + private Visual HitTestFirst(IVisualNode root, Point p, Func? filter) { using var enumerator = new HitTestEnumerator(root, filter, p, Root); @@ -193,14 +193,14 @@ namespace Avalonia.Rendering.SceneGraph return enumerator.Current; } - private class HitTestEnumerable : IEnumerable + private class HitTestEnumerable : IEnumerable { private readonly IVisualNode _root; - private readonly Func? _filter; + private readonly Func? _filter; private readonly IVisualNode _sceneRoot; private readonly Point _point; - public HitTestEnumerable(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) + public HitTestEnumerable(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) { _root = root; _filter = filter; @@ -208,7 +208,7 @@ namespace Avalonia.Rendering.SceneGraph _sceneRoot = sceneRoot; } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return new HitTestEnumerator(_root, _filter, _point, _sceneRoot); } @@ -219,15 +219,15 @@ namespace Avalonia.Rendering.SceneGraph } } - private struct HitTestEnumerator : IEnumerator + private struct HitTestEnumerator : IEnumerator { private readonly PooledStack _nodeStack; - private readonly Func? _filter; + private readonly Func? _filter; private readonly IVisualNode _sceneRoot; - private IVisual? _current; + private Visual? _current; private readonly Point _point; - public HitTestEnumerator(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) + public HitTestEnumerator(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) { _nodeStack = new PooledStack(); _nodeStack.Push(new Entry(root, false, null, true)); @@ -282,7 +282,7 @@ namespace Avalonia.Rendering.SceneGraph throw new NotSupportedException(); } - public IVisual Current => _current!; + public Visual Current => _current!; object IEnumerator.Current => Current; diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs index 0ceb44ed75..d54bd3fad8 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public bool Update(Scene scene, IVisual visual) + public bool Update(Scene scene, Visual visual) { _ = scene ?? throw new ArgumentNullException(nameof(scene)); _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -120,7 +120,7 @@ namespace Avalonia.Rendering.SceneGraph return false; } - private static VisualNode? FindExistingAncestor(Scene scene, IVisual visual) + private static VisualNode? FindExistingAncestor(Scene scene, Visual visual) { var node = scene.FindNode(visual); @@ -151,7 +151,7 @@ namespace Avalonia.Rendering.SceneGraph return (VisualNode)node; } - private static object GetOrCreateChildNode(Scene scene, IVisual child, VisualNode parent) + private static object GetOrCreateChildNode(Scene scene, Visual child, VisualNode parent) { var result = (VisualNode?)scene.FindNode(child); @@ -259,11 +259,11 @@ namespace Avalonia.Rendering.SceneGraph catch { } var transformed = new TransformedBounds(new Rect(visual.Bounds.Size), clip, node.Transform); - visual.TransformedBounds = transformed; + visual.SetTransformedBounds(transformed); if (forceRecurse) { - var visualChildren = (IList) visual.VisualChildren; + var visualChildren = (IList) visual.VisualChildren; node.TryPreallocateChildren(visualChildren.Count); @@ -278,7 +278,7 @@ namespace Avalonia.Rendering.SceneGraph if (visual.HasNonUniformZIndexChildren) { - var sortedChildren = new (IVisual visual, int index)[count]; + var sortedChildren = new (Visual visual, int index)[count]; for (var i = 0; i < count; i++) { @@ -318,7 +318,7 @@ namespace Avalonia.Rendering.SceneGraph } } - private void UpdateSize(Scene scene) + private static void UpdateSize(Scene scene) { var renderRoot = scene.Root.Visual as IRenderRoot; var newSize = renderRoot?.ClientSize ?? scene.Root.Visual.Bounds.Size; @@ -352,7 +352,7 @@ namespace Avalonia.Rendering.SceneGraph } } - private static VisualNode CreateNode(Scene scene, IVisual visual, VisualNode parent) + private static VisualNode CreateNode(Scene scene, Visual visual, VisualNode parent) { var node = new VisualNode(visual, parent); node.LayerRoot = parent.LayerRoot; @@ -379,7 +379,7 @@ namespace Avalonia.Rendering.SceneGraph scene.Layers[node.LayerRoot!].Dirty.Add(node.Bounds); - node.Visual.TransformedBounds = null; + node.Visual.SetTransformedBounds(null); if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual) { @@ -455,7 +455,7 @@ namespace Avalonia.Rendering.SceneGraph } // HACK: Disabled layers because they're broken in current renderer. See #2244. - private static bool ShouldStartLayer(IVisual visual) => false; + private static bool ShouldStartLayer(Visual visual) => false; private static IGeometryImpl? CreateLayerGeometryClip(VisualNode node) { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs index e9474f6e98..a5e3b88188 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs @@ -1,6 +1,5 @@ using Avalonia.Media; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { @@ -14,7 +13,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The visual at the root of the layer. /// The distance from the scene root. - public SceneLayer(IVisual layerRoot, int distanceFromRoot) + public SceneLayer(Visual layerRoot, int distanceFromRoot) { LayerRoot = layerRoot; Dirty = new DirtyRects(); @@ -39,7 +38,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the visual at the root of the layer. /// - public IVisual LayerRoot { get; } + public Visual LayerRoot { get; } /// /// Gets the distance of the layer root from the root of the scene. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs index 16d704e5f6..8a997c5ace 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs @@ -10,15 +10,15 @@ namespace Avalonia.Rendering.SceneGraph /// public class SceneLayers : IEnumerable { - private readonly IVisual _root; + private readonly Visual _root; private readonly List _inner; - private readonly Dictionary _index; + private readonly Dictionary _index; /// /// Initializes a new instance of the class. /// /// The scene's root visual. - public SceneLayers(IVisual root) : this(root, 0) + public SceneLayers(Visual root) : this(root, 0) { } @@ -27,12 +27,12 @@ namespace Avalonia.Rendering.SceneGraph /// /// The scene's root visual. /// Initial layer capacity. - public SceneLayers(IVisual root, int capacity) + public SceneLayers(Visual root, int capacity) { _root = root; _inner = new List(capacity); - _index = new Dictionary(capacity); + _index = new Dictionary(capacity); } /// @@ -71,14 +71,14 @@ namespace Avalonia.Rendering.SceneGraph /// /// The layer's root visual. /// The layer. - public SceneLayer this[IVisual visual] => _index[visual]; + public SceneLayer this[Visual visual] => _index[visual]; /// /// Adds a layer to the scene. /// /// The root visual of the layer. /// The created layer. - public SceneLayer Add(IVisual layerRoot) + public SceneLayer Add(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); @@ -115,7 +115,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// True if a layer exists with the specified root visual, otherwise false. /// - public bool Exists(IVisual layerRoot) + public bool Exists(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); @@ -127,7 +127,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The root visual. /// The layer if found, otherwise null. - public SceneLayer? Find(IVisual layerRoot) + public SceneLayer? Find(Visual layerRoot) { _index.TryGetValue(layerRoot, out var result); return result; @@ -138,7 +138,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The root visual. /// The layer. - public SceneLayer GetOrAdd(IVisual layerRoot) + public SceneLayer GetOrAdd(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); @@ -155,7 +155,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The root visual. /// True if a matching layer was removed, otherwise false. - public bool Remove(IVisual layerRoot) + public bool Remove(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs index 8a2f1f5073..a991f2f657 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs @@ -10,7 +10,7 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { /// - /// A node in the low-level scene graph representing an . + /// A node in the low-level scene graph representing an . /// internal class VisualNode : IVisualNode { @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The visual that this node represents. /// The parent scene graph node, if any. - public VisualNode(IVisual visual, IVisualNode? parent) + public VisualNode(Visual visual, IVisualNode? parent) { Visual = visual ?? throw new ArgumentNullException(nameof(visual)); Parent = parent; @@ -39,7 +39,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public IVisual Visual { get; } + public Visual Visual { get; } /// public IVisualNode? Parent { get; } @@ -98,7 +98,7 @@ namespace Avalonia.Rendering.SceneGraph /// public bool OpacityChanged { get; private set; } - public IVisual? LayerRoot { get; set; } + public Visual? LayerRoot { get; set; } /// public IReadOnlyList Children => _children ?? EmptyChildren; diff --git a/src/Avalonia.Base/Rendering/ZIndexComparer.cs b/src/Avalonia.Base/Rendering/ZIndexComparer.cs index 4566b905c4..c9240668b4 100644 --- a/src/Avalonia.Base/Rendering/ZIndexComparer.cs +++ b/src/Avalonia.Base/Rendering/ZIndexComparer.cs @@ -4,11 +4,11 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering { - public class ZIndexComparer : IComparer + public class ZIndexComparer : IComparer { public static readonly ZIndexComparer Instance = new ZIndexComparer(); - public static readonly Comparison ComparisonInstance = Instance.Compare; + public static readonly Comparison ComparisonInstance = Instance.Compare; - public int Compare(IVisual? x, IVisual? y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); + public int Compare(Visual? x, Visual? y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); } } diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index bba9685ed8..6043175eee 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using Avalonia.Animation; using Avalonia.Collections; @@ -11,6 +12,7 @@ using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.LogicalTree; +using Avalonia.PropertyStore; using Avalonia.Styling; namespace Avalonia @@ -23,7 +25,15 @@ namespace Avalonia /// - Implements to form part of a logical tree. /// - A collection of class strings for custom styling. /// - public class StyledElement : Animatable, IDataContextProvider, IStyledElement, ISetLogicalParent, ISetInheritanceParent + public class StyledElement : Animatable, + IDataContextProvider, + ILogical, + IResourceHost, + IStyleHost, + IStyleable, + ISetLogicalParent, + ISetInheritanceParent, + ISupportInitialize { /// /// Defines the property. @@ -43,14 +53,14 @@ namespace Avalonia /// /// Defines the property. /// - public static readonly DirectProperty ParentProperty = - AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); + public static readonly DirectProperty ParentProperty = + AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); /// /// Defines the property. /// - public static readonly DirectProperty TemplatedParentProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty TemplatedParentProperty = + AvaloniaProperty.RegisterDirect( nameof(TemplatedParent), o => o.TemplatedParent, (o ,v) => o.TemplatedParent = v); @@ -69,10 +79,11 @@ namespace Avalonia private IAvaloniaList? _logicalChildren; private IResourceDictionary? _resources; private Styles? _styles; - private bool _styled; - private ITemplatedControl? _templatedParent; + private bool _stylesApplied; + private bool _themeApplied; + private bool _templatedParentThemeApplied; + private AvaloniaObject? _templatedParent; private bool _dataContextUpdating; - private bool _hasPromotedTheme; private ControlTheme? _implicitTheme; /// @@ -141,7 +152,7 @@ namespace Avalonia set { - if (_styled) + if (_stylesApplied) { throw new InvalidOperationException("Cannot set Name : styled element already styled."); } @@ -231,7 +242,7 @@ namespace Avalonia /// /// Gets the styled element whose lookless template this styled element is part of. /// - public ITemplatedControl? TemplatedParent + public AvaloniaObject? TemplatedParent { get => _templatedParent; internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value); @@ -249,7 +260,7 @@ namespace Avalonia /// /// Gets the styled element's logical children. /// - protected IAvaloniaList LogicalChildren + protected internal IAvaloniaList LogicalChildren { get { @@ -282,7 +293,7 @@ namespace Avalonia /// /// Gets the styled element's logical parent. /// - public IStyledElement? Parent { get; private set; } + public StyledElement? Parent { get; private set; } /// /// Gets the styled element's logical parent. @@ -344,46 +355,48 @@ namespace Avalonia /// Applies styling to the control if the control is initialized and styling is not /// already applied. /// + /// + /// The styling system will automatically apply styling when required, so it should not + /// usually be necessary to call this method manually. + /// /// /// A value indicating whether styling is now applied to the control. /// - protected bool ApplyStyling() + public bool ApplyStyling() { - if (_initCount == 0 && !_styled) + if (_initCount == 0 && (!_stylesApplied || !_themeApplied)) { - var styler = AvaloniaLocator.Current.GetService(); - var hasPromotedTheme = _hasPromotedTheme; + GetValueStore().BeginStyling(); - if (styler is object) + try { - GetValueStore().BeginStyling(); + if (!_themeApplied) + { + ApplyControlTheme(); + _themeApplied = true; + } - try + if (!_templatedParentThemeApplied) { - styler.ApplyStyles(this); + ApplyTemplatedParentControlTheme(); + _templatedParentThemeApplied = true; } - finally + + if (!_stylesApplied) { - _styled = true; - GetValueStore().EndStyling(); + ApplyStyles(this); + _stylesApplied = true; } } - - if (hasPromotedTheme) + finally { - _hasPromotedTheme = false; - ClearValue(ThemeProperty); + GetValueStore().EndStyling(); } } - return _styled; + return _stylesApplied; } - /// - /// Detaches all styles from the element and queues a restyle. - /// - protected virtual void InvalidateStyles() => DetachStyles(); - protected void InitializeIfNeeded() { if (_initCount == 0 && !IsInitialized) @@ -453,7 +466,7 @@ namespace Avalonia InheritanceParent = parent as AvaloniaObject; } - Parent = (IStyledElement?)parent; + Parent = (StyledElement?)parent; if (_logicalRoot != null) { @@ -484,8 +497,8 @@ namespace Avalonia #nullable disable RaisePropertyChanged( ParentProperty, - new Optional(old), - new BindingValue(Parent), + new Optional(old), + new BindingValue(Parent), BindingPriority.LocalValue); #nullable enable } @@ -495,52 +508,21 @@ namespace Avalonia /// Sets the styled element's inheritance parent. /// /// The parent. - void ISetInheritanceParent.SetParent(IAvaloniaObject? parent) + void ISetInheritanceParent.SetParent(AvaloniaObject? parent) { - InheritanceParent = parent switch - { - AvaloniaObject ao => ao, - null => null, - _ => throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.") - }; + InheritanceParent = parent; } - ControlTheme? IStyleable.GetEffectiveTheme() - { - var theme = Theme; - - // Explitly set Theme property takes precedence. - if (theme is not null) - return theme; - - // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. - if (_implicitTheme is null) - { - var key = ((IStyleable)this).StyleKey; - - if (this.TryFindResource(key, out var value) && value is ControlTheme t) - _implicitTheme = t; - else - _implicitTheme = s_invalidTheme; - } - - if (_implicitTheme != s_invalidTheme) - return _implicitTheme; - - return null; - } - - void IStyleable.DetachStyles() => DetachStyles(); - void IStyleHost.StylesAdded(IReadOnlyList styles) { - InvalidateStylesOnThisAndDescendents(); + if (HasSettersOrAnimations(styles)) + InvalidateStyles(recurse: true); } void IStyleHost.StylesRemoved(IReadOnlyList styles) { - var allStyles = RecurseStyles(styles); - DetachStylesFromThisAndDescendents(allStyles); + if (FlattenStyles(styles) is { } allStyles) + DetachStyles(allStyles); } protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -638,35 +620,83 @@ namespace Avalonia base.OnPropertyChanged(change); if (change.Property == ThemeProperty) - { - var (oldValue, newValue) = change.GetOldAndNewValue(); + OnControlThemeChanged(); + } - // Changing the theme detaches all styles, meaning that if the theme property was - // set via a style, it will get cleared! To work around this, if the value was - // applied at less than local value priority then promote the value to local value - // priority until styling is re-applied. - if (change.Priority > BindingPriority.LocalValue) - { - Theme = newValue; - _hasPromotedTheme = true; - } - else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue) - { - _hasPromotedTheme = false; - } + private protected virtual void OnControlThemeChanged() + { + var values = GetValueStore(); + values.BeginStyling(); + + 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(); + _templatedParentThemeApplied = false; + } + } + + internal ControlTheme? GetEffectiveTheme() + { + var theme = Theme; + + // Explitly set Theme property takes precedence. + if (theme is not null) + return theme; - InvalidateStyles(); + // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. + if (_implicitTheme is null) + { + var key = ((IStyleable)this).StyleKey; - if (oldValue is not null) - DetachControlThemeFromTemplateChildren(oldValue); + if (this.TryFindResource(key, out var value) && value is ControlTheme t) + _implicitTheme = t; + else + _implicitTheme = s_invalidTheme; } + + if (_implicitTheme != s_invalidTheme) + return _implicitTheme; + + return null; } - internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme) + internal virtual void InvalidateStyles(bool recurse) { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.Style); } + finally { values.EndStyling(); } + + _stylesApplied = false; + + if (recurse && GetInheritanceChildren() is { } children) + { + var childCount = children.Count; + for (var i = 0; i < childCount; ++i) + (children[i] as StyledElement)?.InvalidateStyles(recurse); + } } - private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) + private static void DataContextNotifying(AvaloniaObject o, bool updateStarted) { if (o is StyledElement element) { @@ -730,6 +760,80 @@ namespace Avalonia } } + private void ApplyControlTheme() + { + if (GetEffectiveTheme() is { } theme) + ApplyControlTheme(theme, FrameType.Theme); + } + + private void ApplyTemplatedParentControlTheme() + { + if ((TemplatedParent as StyledElement)?.GetEffectiveTheme() is { } parentTheme) + { + ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme); + } + } + + private void ApplyControlTheme(ControlTheme theme, FrameType type) + { + Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme); + + if (theme.BasedOn is ControlTheme basedOn) + ApplyControlTheme(basedOn, type); + + theme.TryAttach(this, type); + + if (theme.HasChildren) + { + foreach (var child in theme.Children) + ApplyStyle(child, null, type); + } + } + + private void ApplyStyles(IStyleHost host) + { + var parent = host.StylingParent; + if (parent != null) + ApplyStyles(parent); + + if (host.IsStylesInitialized) + { + foreach (var style in host.Styles) + ApplyStyle(style, host, FrameType.Style); + } + } + + private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type) + { + if (style is Style s) + s.TryAttach(this, host, type); + + foreach (var child in style.Children) + 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)) @@ -748,6 +852,7 @@ namespace Avalonia { _logicalRoot = e.Root; + ReevaluateImplicitTheme(); ApplyStyling(); NotifyResourcesChanged(propagate: false); @@ -772,8 +877,7 @@ namespace Avalonia if (_logicalRoot != null) { _logicalRoot = null; - _implicitTheme = null; - DetachStyles(); + InvalidateStyles(recurse: false); OnDetachedFromLogicalTree(e); DetachedFromLogicalTree?.Invoke(this, e); @@ -835,70 +939,81 @@ namespace Avalonia } } - private void DetachStyles(IReadOnlyList? styles = null) + private void DetachStyles(IReadOnlyList - + @@ -337,10 +337,6 @@ - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index 3cf819c4b8..37e9b71185 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -103,17 +103,18 @@ RenderTransformOrigin="50%,50%" Stretch="None" Stroke="{DynamicResource ExpanderChevronForeground}" - StrokeThickness="1" /> - - - + StrokeThickness="1"> + + + + - - - - - - - - - - - - - - + + + + + + + + + + - diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs index af9d305043..56c4cbeac6 100644 --- a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Styling; @@ -16,9 +17,10 @@ namespace Avalonia.Themes.Simple /// /// Initializes a new instance of the class. /// - public SimpleTheme() + /// The parent's service provider. + public SimpleTheme(IServiceProvider? sp = null) { - AvaloniaXamlLoader.Load(this); + AvaloniaXamlLoader.Load(sp, this); _simpleDark = (IResourceDictionary)GetAndRemove("BaseDark"); _simpleLight = (IResourceDictionary)GetAndRemove("BaseLight"); diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index 621e6fabd7..c762349c1c 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -12,4 +12,5 @@ + diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index 632a7d39a2..4ae1c1599f 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -9,7 +9,7 @@ namespace Avalonia.X11 { class X11IconLoader : IPlatformIconLoader { - IWindowIconImpl LoadIcon(Bitmap bitmap) + static IWindowIconImpl LoadIcon(Bitmap bitmap) { var rv = new X11IconData(bitmap); bitmap.Dispose(); diff --git a/src/Avalonia.X11/X11ImmediateRendererProxy.cs b/src/Avalonia.X11/X11ImmediateRendererProxy.cs index 7280567bb3..293e5110f7 100644 --- a/src/Avalonia.X11/X11ImmediateRendererProxy.cs +++ b/src/Avalonia.X11/X11ImmediateRendererProxy.cs @@ -14,7 +14,7 @@ namespace Avalonia.X11 private bool _running; private object _lock = new object(); - public X11ImmediateRendererProxy(IVisual root, IRenderLoop loop) + public X11ImmediateRendererProxy(Visual root, IRenderLoop loop) { _loop = loop; _renderer = new ImmediateRenderer(root); @@ -45,24 +45,24 @@ namespace Avalonia.X11 remove => _renderer.SceneInvalidated -= value; } - public void AddDirty(IVisual visual) + public void AddDirty(Visual visual) { lock (_lock) _invalidated = true; _renderer.AddDirty(visual); } - public IEnumerable HitTest(Point p, IVisual root, Func filter) + public IEnumerable HitTest(Point p, Visual root, Func filter) { return _renderer.HitTest(p, root, filter); } - public IVisual HitTestFirst(Point p, IVisual root, Func filter) + public Visual HitTestFirst(Point p, Visual root, Func filter) { return _renderer.HitTestFirst(p, root, filter); } - public void RecalculateChildren(IVisual visual) + public void RecalculateChildren(Visual visual) { _renderer.RecalculateChildren(visual); } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 96dc16e186..cbb782edd0 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Controls.Platform; -using Avalonia.Dialogs; using Avalonia.FreeDesktop; using Avalonia.FreeDesktop.DBusIme; using Avalonia.Input; @@ -85,7 +84,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new LinuxMountedVolumeInfoProvider()) .Bind().ToConstant(new X11PlatformLifetimeEvents(this)); - X11Screens = Avalonia.X11.X11Screens.Init(this); + X11Screens = X11.X11Screens.Init(this); Screens = new X11Screens(X11Screens); if (Info.XInputVersion != null) { @@ -143,7 +142,7 @@ namespace Avalonia.X11 throw new NotSupportedException(); } - bool EnableIme(X11PlatformOptions options) + static bool EnableIme(X11PlatformOptions options) { // Disable if explicitly asked by user var avaloniaImModule = Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE"); @@ -164,8 +163,8 @@ namespace Avalonia.X11 return isCjkLocale; } - - bool ShouldUseXim() + + static bool ShouldUseXim() { // Check if we are forbidden from using IME if (Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE") == "none" diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 3f0a6aeb67..7e46606c36 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -34,6 +34,7 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Diagnostics.CodeAnalysis; // ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable IdentifierTypo // ReSharper disable MemberCanBePrivate.Global @@ -654,7 +655,8 @@ namespace Avalonia.X11 { return type.ToString (); } } - + + [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public static string ToString (object ev) { string result = string.Empty; diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index ca987d0a07..257580a5ec 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/src/Avalonia.X11/X11Window.Ime.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Raw; diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 0b5ae281f4..2f84e15b32 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; -using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; using System.Threading; @@ -398,7 +396,7 @@ namespace Avalonia.X11 { RenderOnlyOnRenderThread = true } - : (IRenderer)new X11ImmediateRendererProxy(root, loop); + : (IRenderer)new X11ImmediateRendererProxy((Visual)root, loop); } void OnEvent(ref XEvent ev) @@ -683,7 +681,7 @@ namespace Avalonia.X11 } - RawInputModifiers TranslateModifiers(XModifierMask state) + static RawInputModifiers TranslateModifiers(XModifierMask state) { var rv = default(RawInputModifiers); if (state.HasAllFlags(XModifierMask.Button1Mask)) diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj index 4537f8d73e..a2f1b55b6f 100644 --- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj +++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj index 2564140a03..014d387cb2 100644 --- a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj +++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Browser/Avalonia.Browser/Cursor.cs b/src/Browser/Avalonia.Browser/Cursor.cs index ec0dcc51b4..7d77f6e0d2 100644 --- a/src/Browser/Avalonia.Browser/Cursor.cs +++ b/src/Browser/Avalonia.Browser/Cursor.cs @@ -20,9 +20,9 @@ namespace Avalonia.Browser /// public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback) { - Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; + Value = FormattableString.Invariant($"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"); } - + /// /// Create a cursor from url to *.cur file. /// @@ -30,15 +30,15 @@ namespace Avalonia.Browser { Value = $"url('{url}'), {ToKeyword(fallback)}"; } - + /// /// Create a cursor from png/svg and hotspot position /// public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback) { - Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}"; + Value = FormattableString.Invariant($"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}"); } - + private static string ToKeyword(StandardCursorType type) => type switch { StandardCursorType.Hand => "pointer", @@ -49,16 +49,16 @@ namespace Avalonia.Browser StandardCursorType.None => "none", StandardCursorType.Wait => "progress", StandardCursorType.AppStarting => "wait", - + StandardCursorType.DragMove => "move", StandardCursorType.DragCopy => "copy", StandardCursorType.DragLink => "alias", - + StandardCursorType.UpArrow => "default",/*not found matching one*/ StandardCursorType.SizeWestEast => "ew-resize", StandardCursorType.SizeNorthSouth => "ns-resize", StandardCursorType.SizeAll => "move", - + StandardCursorType.TopSide => "n-resize", StandardCursorType.BottomSide => "s-resize", StandardCursorType.LeftSide => "w-resize", @@ -67,10 +67,10 @@ namespace Avalonia.Browser StandardCursorType.TopRightCorner => "ne-resize", StandardCursorType.BottomLeftCorner => "sw-resize", StandardCursorType.BottomRightCorner => "se-resize", - + _ => Default, }; - + public void Dispose() {} } diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderSession.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderSession.cs index 81b37da7bd..3e4cf31dda 100644 --- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderSession.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderSession.cs @@ -9,7 +9,8 @@ namespace Avalonia.Browser.Skia public BrowserSkiaGpuRenderSession(BrowserSkiaSurface browserSkiaSurface, GRBackendRenderTarget renderTarget) { - _surface = SKSurface.Create(browserSkiaSurface.Context, renderTarget, browserSkiaSurface.Origin, browserSkiaSurface.ColorType); + _surface = SKSurface.Create(browserSkiaSurface.Context, renderTarget, browserSkiaSurface.Origin, + browserSkiaSurface.ColorType, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); GrContext = browserSkiaSurface.Context; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index 5cebbb6829..366669d10d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -8,5 +8,6 @@ - + + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj index 1dc7ce5e99..d74cc0b747 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -1,4 +1,4 @@ - + net6.0;netstandard2.0 @@ -13,6 +13,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs index 9393bb0aa4..b4c951fc5e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using Avalonia.Markup.Xaml.XamlIl; - +#nullable enable namespace Avalonia.Markup.Xaml { public static class AvaloniaRuntimeXamlLoader @@ -17,31 +18,15 @@ namespace Avalonia.Markup.Xaml /// The URI of the XAML being loaded. /// Indicates whether the XAML is being loaded in design mode. /// The loaded object. - public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false) + public static object Load(string xaml, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null, bool designMode = false) { - Contract.Requires(xaml != null); + xaml = xaml ?? throw new ArgumentNullException(nameof(xaml)); using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) { return Load(stream, localAssembly, rootInstance, uri, designMode); } } - - /// - /// Loads XAML from a string. - /// - /// The string containing the XAML. - /// Xaml loader configuration. - /// The loaded object. - public static object Load(string xaml, RuntimeXamlLoaderConfiguration configuration) - { - Contract.Requires(xaml != null); - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) - { - return Load(stream, configuration); - } - } /// /// Loads XAML from a stream. @@ -52,19 +37,28 @@ namespace Avalonia.Markup.Xaml /// The URI of the XAML being loaded. /// Indicates whether the XAML is being loaded in design mode. /// The loaded object. - public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null, + public static object Load(Stream stream, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null, bool designMode = false) - => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode, false); - + => AvaloniaXamlIlRuntimeCompiler.Load(new RuntimeXamlLoaderDocument(uri, rootInstance, stream), + new RuntimeXamlLoaderConfiguration { DesignMode = designMode, LocalAssembly = localAssembly }); + /// /// Loads XAML from a stream. /// - /// The stream containing the XAML. + /// The stream containing the XAML. /// Xaml loader configuration. /// The loaded object. - public static object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration) - => AvaloniaXamlIlRuntimeCompiler.Load(stream, configuration.LocalAssembly, configuration.RootInstance, - configuration.BaseUri, configuration.DesignMode, configuration.UseCompiledBindingsByDefault); + public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration? configuration = null) + => AvaloniaXamlIlRuntimeCompiler.Load(document, configuration ?? new RuntimeXamlLoaderConfiguration()); + + /// + /// Loads group of XAML files from a stream. + /// + /// Collection of documents. + /// Xaml loader configuration. + /// The loaded objects per each input document. + public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration? configuration = null) + => AvaloniaXamlIlRuntimeCompiler.LoadGroup(documents, configuration ?? new RuntimeXamlLoaderConfiguration()); /// /// Parse XAML from a string. @@ -72,7 +66,7 @@ namespace Avalonia.Markup.Xaml /// The string containing the XAML. /// Default assembly for clr-namespace:. /// The loaded object. - public static object Parse(string xaml, Assembly localAssembly = null) + public static object Parse(string xaml, Assembly? localAssembly = null) => Load(xaml, localAssembly); /// @@ -82,7 +76,7 @@ namespace Avalonia.Markup.Xaml /// >The string containing the XAML. /// >Default assembly for clr-namespace:. /// The loaded object. - public static T Parse(string xaml, Assembly localAssembly = null) + public static T Parse(string xaml, Assembly? localAssembly = null) => (T)Parse(xaml, localAssembly); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index b4b258e53e..2bb91e8a32 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Platform; +using XamlX.Ast; using XamlX.Transform; using XamlX.TypeSystem; using XamlX.IL; @@ -150,12 +151,12 @@ namespace Avalonia.Markup.Xaml.XamlIl } - static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault) + static object LoadSre(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) { var success = false; try { - var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault); + var rv = LoadSreCore(document, configuration); success = true; return rv; } @@ -166,45 +167,102 @@ namespace Avalonia.Markup.Xaml.XamlIl } } + static IReadOnlyList LoadGroupSre(IReadOnlyCollection documents, + RuntimeXamlLoaderConfiguration configuration) + { + var success = false; + try + { + var rv = LoadGroupSreCore(documents, configuration); + success = true; + return rv; + } + finally + { + if( _sreCanSave) + DumpRuntimeCompilationResults(); + } + } - static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault) + static IReadOnlyList LoadGroupSreCore(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration configuration) { - InitializeSre(); + var localAssembly = configuration.LocalAssembly; if (localAssembly?.GetName() != null) EmitIgnoresAccessCheckToAttribute(localAssembly.GetName()); var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly); - var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); - var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N")); + var clrPropertyBuilder = _sreBuilder.DefineType("ClrProperties_" + Guid.NewGuid().ToString("N")); var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N")); var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N")); - + var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm, - _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, - new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), - new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)), - new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))), + _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, + new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), + new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)), + new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))), _sreEmitMappings, - _sreContextType) { EnableIlVerification = true, DefaultCompileBindings = useCompiledBindingsByDefault }; + _sreContextType) + { + EnableIlVerification = true, + DefaultCompileBindings = configuration.UseCompiledBindingsByDefault, + IsDesignMode = configuration.DesignMode + }; - IXamlType overrideType = null; - if (rootInstance != null) + var parsedDocuments = new List(); + var originalDocuments = new List(); + + foreach (var document in documents) { - overrideType = _sreTypeSystem.GetType(rootInstance.GetType()); + string xaml; + using (var sr = new StreamReader(document.XamlStream)) + xaml = sr.ReadToEnd(); + + IXamlType overrideType = null; + if (document.RootInstance != null) + { + overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType()); + } + + var parsed = compiler.Parse(xaml, overrideType); + compiler.Transform(parsed); + + var xamlName = GetSafeUriIdentifier(document.BaseUri) + ?? document.RootInstance?.GetType().Name + ?? ((IXamlAstValueNode)parsed.Root).Type.GetClrType().Name; + var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + xamlName); + var builder = _sreTypeSystem.CreateTypeBuilder(tb); + parsedDocuments.Add(new XamlDocumentResource(parsed, document.BaseUri?.ToString(), null, null, + builder, + compiler.DefinePopulateMethod(builder, parsed, AvaloniaXamlIlCompiler.PopulateName, true), + document.RootInstance is null ? compiler.DefineBuildMethod(builder, parsed, AvaloniaXamlIlCompiler.BuildName, true) : null)); + originalDocuments.Add(document); } - compiler.IsDesignMode = isDesignMode; - compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType); - var created = tb.CreateTypeInfo(); + compiler.TransformGroup(parsedDocuments); + + var createdTypes = parsedDocuments.Select(document => + { + compiler.Compile(document.XamlDocument, document.TypeBuilder, document.PopulateMethod, + document.BuildMethod, document.Uri, document.FileSource); + return _sreTypeSystem.GetType(document.TypeBuilder.CreateType()); + }).ToArray(); + clrPropertyBuilder.CreateTypeInfo(); indexerClosureType.CreateTypeInfo(); trampolineBuilder.CreateTypeInfo(); - return LoadOrPopulate(created, rootInstance); + return createdTypes.Zip(originalDocuments, (l, r) => (l, r)) + .Select(t => LoadOrPopulate(t.Item1, t.Item2.RootInstance, t.Item2.ServiceProvider)) + .ToArray(); + } + + static object LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) + { + return LoadGroupSreCore(new[] { document }, configuration).Single(); } #endif - static object LoadOrPopulate(Type created, object rootInstance) + static object LoadOrPopulate(Type created, object rootInstance, IServiceProvider parentServiceProvider) { var isp = Expression.Parameter(typeof(IServiceProvider)); @@ -215,6 +273,8 @@ namespace Avalonia.Markup.Xaml.XamlIl var populateCb = Expression.Lambda>( Expression.Call(populate, isp, Expression.Convert(epar, populate.GetParameters()[1].ParameterType)), isp, epar).Compile(); + + var serviceProvider = XamlIlRuntimeHelpers.CreateRootServiceProviderV3(parentServiceProvider); if (rootInstance == null) { @@ -226,7 +286,7 @@ namespace Avalonia.Markup.Xaml.XamlIl { overrideField.SetValue(null, new Action( - target => { populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), target); })); + target => { populateCb(serviceProvider, target); })); try { return Activator.CreateInstance(targetType); @@ -240,28 +300,46 @@ namespace Avalonia.Markup.Xaml.XamlIl var createCb = Expression.Lambda>( Expression.Convert(Expression.Call( created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile(); - return createCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2()); + return createCb(serviceProvider); } else { - populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), rootInstance); + populateCb(serviceProvider, rootInstance); return rootInstance; } } - public static object Load(Stream stream, Assembly localAssembly, object rootInstance, Uri uri, - bool isDesignMode, bool useCompiledBindingsByDefault) + public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) { +#if RUNTIME_XAML_CECIL string xaml; - using (var sr = new StreamReader(stream)) + using (var sr = new StreamReader(document.XamlStream)) xaml = sr.ReadToEnd(); + return LoadCecil(xaml, configuration.LocalAssembly, document.RootInstance,document.BaseUri, configuration.UseCompiledBindingsByDefault); +#else + return LoadSre(document, configuration); +#endif + } + + public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration configuration) + { #if RUNTIME_XAML_CECIL - return LoadCecil(xaml, localAssembly, rootInstance, uri, useCompiledBindingsByDefault); + throw new NotImplementedException("Load group was not implemented for the Cecil backend"); #else - return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault); + return LoadGroupSre(documents, configuration); #endif } + private static string GetSafeUriIdentifier(Uri uri) + { + return uri?.ToString() + .Replace(":", "_") + .Replace("/", "_") + .Replace("?", "_") + .Replace("=", "_") + .Replace(".", "_"); + } + #if RUNTIME_XAML_CECIL private static Dictionary populate, Func build)> @@ -303,12 +381,7 @@ namespace Avalonia.Markup.Xaml.XamlIl overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName); } - var safeUri = uri.ToString() - .Replace(":", "_") - .Replace("/", "_") - .Replace("?", "_") - .Replace("=", "_") - .Replace(".", "_"); + var safeUri = GetSafeUriIdentifier(uri); if (_cecilGeneratedCache.TryGetValue(safeUri, out var cached)) return LoadOrPopulate(cached, rootInstance); @@ -335,14 +408,14 @@ namespace Avalonia.Markup.Xaml.XamlIl { DefaultCompileBindings = useCompiledBindingsByDefault }; - compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType); + compiler.ParseAndCompile(xaml, uri.ToString(), null, tb, overrideType); var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll"); using(var f = File.Create(asmPath)) asm.Write(f); var loaded = Assembly.LoadFile(asmPath) .GetTypes().First(x => x.Name == safeUri); _cecilGeneratedCache[safeUri] = loaded; - return LoadOrPopulate(loaded, rootInstance); + return LoadOrPopulate(loaded, rootInstance, null); } #endif } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index e601701d5c..b8350c3f11 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; - +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlX; using XamlX.Ast; @@ -56,8 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlSetterTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(), new AvaloniaXamlIlTransitionsTypeMetadataTransformer(), - new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(), - new AvaloniaXamlIlAssetIncludeTransformer() + new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer() ); InsertBefore( new AvaloniaXamlIlOptionMarkupExtensionTransformer()); @@ -83,6 +82,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter()); Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter()); + + GroupTransformers = new() + { + new AvaloniaXamlIncludeTransformer() + }; } public AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings, @@ -115,7 +119,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions set => _bindingTransformer.CompileBindingsByDefault = value; } - public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder tb, IXamlType overrideRootType) + public List GroupTransformers { get; } + + public void TransformGroup(IReadOnlyCollection documents, bool strict = true) + { + var ctx = new AstGroupTransformationContext(documents, _configuration, strict); + foreach (var transformer in GroupTransformers) + { + foreach (var doc in documents) + { + var root = doc.XamlDocument.Root; + ctx.CurrentDocument = doc; + ctx.RootObject = (IXamlAstValueNode)root; + ctx.VisitChildren(ctx.RootObject, transformer); + root = ctx.Visit(root, transformer); + + doc.XamlDocument.Root = root; + } + } + } + + public XamlDocument Parse(string xaml, IXamlType overrideRootType) { var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary { @@ -148,9 +172,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions OverrideRootType(parsed, rootType); + return parsed; + } + + public void Compile(XamlDocument document, IXamlTypeBuilder tb, IXamlMethodBuilder populateMethod, IXamlMethodBuilder buildMethod, string baseUri, IFileSource fileSource) + { + Compile(document, _contextType, populateMethod, buildMethod, + _configuration.TypeMappings.XmlNamespaceInfoProvider == null ? + null : + tb.DefineSubType(_configuration.WellKnownTypes.Object, + "__AvaloniaXamlIlNsInfo", false), (name, bt) => tb.DefineSubType(bt, name, false), + (s, returnType, parameters) => tb.DefineDelegateSubType(s, false, returnType, parameters), baseUri, + fileSource); + } + + public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder tb, IXamlType overrideRootType) + { + var parsed = Parse(xaml, overrideRootType); + Transform(parsed); Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource); - } public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs index b4999136a4..e9d812a465 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -57,7 +57,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions XmlNamespaceInfoProvider = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"), DeferredContentPropertyAttributes = { typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute") }, - DeferredContentExecutorCustomizationDefaultTypeParameter = typeSystem.GetType("Avalonia.Controls.IControl"), + DeferredContentExecutorCustomizationDefaultTypeParameter = typeSystem.GetType("Avalonia.Controls.Control"), DeferredContentExecutorCustomizationTypeParameterDeferredContentAttributePropertyNames = new List { "TemplateResultType" diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 64fdfe155d..925bf0a4fa 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -274,7 +274,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { var uriText = text.Trim(); - var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.Absolute : UriKind.Relative); + var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.RelativeOrAbsolute : UriKind.Relative); if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _)) { diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs new file mode 100644 index 0000000000..32bf37431f --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using XamlX; +using XamlX.Ast; +using XamlX.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; + +internal class AstGroupTransformationContext : AstTransformationContext +{ + public AstGroupTransformationContext(IReadOnlyCollection documents, TransformerConfiguration configuration, bool strictMode = true) + : base(configuration, null, strictMode) + { + Documents = documents; + } + + public IXamlDocumentResource CurrentDocument { get; set; } + + public IReadOnlyCollection Documents { get; } + + public new IXamlAstNode ParseError(string message, IXamlAstNode node) => + Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node)); + + public new IXamlAstNode ParseError(string message, IXamlAstNode offender, IXamlAstNode ret) => + Error(ret, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, offender)); + + class Visitor : IXamlAstVisitor + { + private readonly AstGroupTransformationContext _context; + private readonly IXamlAstGroupTransformer _transformer; + + public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer) + { + _context = context; + _transformer = transformer; + } + + public IXamlAstNode Visit(IXamlAstNode node) + { +#if Xaml_DEBUG + return _transformer.Transform(_context, node); +#else + try + { + return _transformer.Transform(_context, node); + } + catch (Exception e) when (!(e is XmlException)) + { + throw new XamlDocumentParseException( + _context.CurrentDocument?.FileSource?.FilePath, + "Internal compiler error while transforming node " + node + ":\n" + e, + node); + } +#endif + } + + public void Push(IXamlAstNode node) => _context.PushParent(node); + + public void Pop() => _context.PopParent(); + } + + public IXamlAstNode Visit(IXamlAstNode root, IXamlAstGroupTransformer transformer) + { + root = root.Visit(new Visitor(this, transformer)); + return root; + } + + public void VisitChildren(IXamlAstNode root, IXamlAstGroupTransformer transformer) + { + root.VisitChildren(new Visitor(this, transformer)); + } +} + +internal interface IXamlAstGroupTransformer +{ + IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node); +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs new file mode 100644 index 0000000000..1bb05e238c --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using Avalonia.Platform; +using XamlX; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.Transform.Transformers; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; + +internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer +{ + public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node) + { + if (node is not XamlValueWithManipulationNode valueNode + || valueNode.Value is not XamlAstNewClrObjectNode objectNode + || (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude + && objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude)) + { + return node; + } + + var nodeTypeName = objectNode.Type.GetClrType().Name; + var expectedLoadedType = objectNode.Type.GetClrType().GetAllProperties() + .FirstOrDefault(p => p.Name == "Loaded")?.PropertyType; + if (expectedLoadedType is null) + { + throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined"); + } + + if (valueNode.Manipulation is not XamlObjectInitializationNode + { + Manipulation: XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty + }) + { + return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node); + } + + // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`. + if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode + || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri + || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath } + || sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind }) + { + // TODO: make it a compiler warning + // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet. + return node; + } + + var uriPath = new Uri(originalAssetPath, (UriKind)uriKind); + if (!uriPath.IsAbsoluteUri) + { + var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null."); + uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath); + } + else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase)) + { + return context.ParseError( + $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", + sourceUriNode, node); + } + + var assetPathUri = Uri.UnescapeDataString(uriPath.AbsoluteUri); + var assetPath = assetPathUri.Replace("avares://", ""); + var assemblyNameSeparator = assetPath.IndexOf('/'); + var assembly = assetPath.Substring(0, assemblyNameSeparator); + var fullTypeName = Path.GetFileNameWithoutExtension(assetPath.Replace('/', '.')); + + // Search file in the current assembly among other XAML resources. + if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, assetPathUri, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument) + { + if (targetDocument.BuildMethod is not null) + { + return FromMethod(context, targetDocument.BuildMethod, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); + } + + if (targetDocument.ClassType is not null) + { + return FromType(context, targetDocument.ClassType, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); + } + + return context.ParseError( + $"Unable to resolve XAML resource \"{assetPathUri}\" in the current assembly.", + sourceUriNode, node); + } + + // If resource wasn't found in the current assembly, search in the others. + if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly) + { + return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode, node); + } + + var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources"); + if (avaResType is null) + { + return context.ParseError( + $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode, node); + } + + var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator); + var buildMethod = avaResType.FindMethod(m => m.Name == relativeName); + if (buildMethod is not null) + { + return FromMethod(context, buildMethod, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); + } + else if (assetAssembly.FindType(fullTypeName) is { } type) + { + return FromType(context, type, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); + } + + return context.ParseError( + $"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly.", + sourceUriNode, node); + } + + private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li, + IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly) + { + if (!expectedLoadedType.IsAssignableFrom(type)) + { + return context.ParseError( + $"Resource \"{assetPathUri}\" is defined as \"{type}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".", + li, fallbackNode); + } + + IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false)); + newObjNode = new AvaloniaXamlIlConstructorServiceProviderTransformer().Transform(context, newObjNode); + newObjNode = new ConstructableObjectTransformer().Transform(context, newObjNode); + return new NewObjectTransformer().Transform(context, newObjNode); + } + + private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlAstNode li, + IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly) + { + if (!expectedLoadedType.IsAssignableFrom(method.ReturnType)) + { + return context.ParseError( + $"Resource \"{assetPathUri}\" is defined as \"{method.ReturnType}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".", + li, fallbackNode); + } + + var sp = context.Configuration.TypeMappings.ServiceProvider; + return new XamlStaticOrTargetedReturnMethodCallNode(li, method, + new[] { new NewServiceProviderNode(sp, li) }); + } + + internal class NewServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack, + IXamlAstEmitableNode + { + public NewServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo) + { + Type = new XamlAstClrTypeReference(lineInfo, type, false); + } + + public IXamlAstTypeReference Type { get; } + public bool NeedsParentStack => true; + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen.Ldloc(context.ContextLocal); + var method = context.GetAvaloniaTypes().RuntimeHelpers + .FindMethod(m => m.Name == "CreateRootServiceProviderV3"); + codeGen.EmitCall(method); + + return XamlILNodeEmitResult.Type(0, Type.GetClrType()); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs new file mode 100644 index 0000000000..b859b3ed59 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs @@ -0,0 +1,17 @@ +using System; +using XamlX.Ast; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; + +#nullable enable + +internal interface IXamlDocumentResource +{ + IXamlMethod? BuildMethod { get; } + IXamlType? ClassType { get; } + string? Uri { get; } + IXamlMethod PopulateMethod { get; } + IFileSource? FileSource { get; } + XamlDocument XamlDocument { get; } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs deleted file mode 100644 index 377a9e72d9..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Linq; -using XamlX; -using XamlX.Ast; -using XamlX.Emit; -using XamlX.IL; -using XamlX.Transform; -using XamlX.TypeSystem; - -namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; - -internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer -{ - public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) - { - if (node is not XamlAstObjectNode objectNode - || (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude - && objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude)) - { - return node; - } - - var nodeTypeName = objectNode.Type.GetClrType().Name; - - var sourceProperty = objectNode.Children.OfType().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source"); - var directives = objectNode.Children.OfType().ToList(); - if (sourceProperty is null - || objectNode.Children.Count != (directives.Count + 1)) - { - throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node); - } - - if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceTextNode) - { - // TODO: make it a compiler warning - // Source value can be set with markup extension instead of a text node, we don't support it here yet. - return node; - } - - var originalAssetPath = sourceTextNode.Text; - if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/"))) - { - return node; - } - - var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers; - var markerMethodName = "Resolve" + nodeTypeName; - var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3); - if (markerMethod is null) - { - throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node); - } - - return new XamlValueWithManipulationNode( - node, - new AssetIncludeMethodNode(node, markerMethod, originalAssetPath), - new XamlManipulationGroupNode(node, directives)); - } - - private class AssetIncludeMethodNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode - { - private readonly IXamlMethod _method; - private readonly string _originalAssetPath; - - public AssetIncludeMethodNode( - IXamlAstNode original, IXamlMethod method, string originalAssetPath) - : base(original) - { - _method = method; - _originalAssetPath = originalAssetPath; - } - - public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _method.ReturnType, false); - - public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) - { - var absoluteSource = _originalAssetPath; - if (absoluteSource.StartsWith("/")) - { - // Avoid Uri class here to avoid potential problems with escaping. - // Keeping string as close to the original as possible. - var absoluteBaseUrl = context.RuntimeContext.BaseUrl; - absoluteSource = absoluteBaseUrl.Substring(0, absoluteBaseUrl.LastIndexOf('/')) + absoluteSource; - } - - codeGen.Ldstr(absoluteSource); - codeGen.Ldc_I4(Line); - codeGen.Ldc_I4(Position); - codeGen.EmitCall(_method); - - return XamlILNodeEmitResult.Type(0, _method.ReturnType); - } - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs index 6cab68e756..76fd07f15c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs @@ -42,7 +42,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (priorityValueSetters.Count > 0) { prop.PossibleSetters = priorityValueSetters; - prop.Values.Insert(0, new XamlConstantNode(node, bindingPriorityType, (int)BindingPriority.TemplatedParent)); + prop.Values.Insert(0, new XamlConstantNode(node, bindingPriorityType, (int)BindingPriority.Template)); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index dd37ae6c93..87a037c16a 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; using XamlX.Emit; using XamlX.IL; using XamlX.Transform; @@ -11,7 +12,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { public IXamlType RuntimeHelpers { get; } public IXamlType AvaloniaObject { get; } - public IXamlType IAvaloniaObject { get; } public IXamlType BindingPriority { get; } public IXamlType AvaloniaObjectExtensions { get; } public IXamlType AvaloniaProperty { get; } @@ -35,7 +35,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType OnExtensionType { get; } public IXamlType UnsetValueType { get; } public IXamlType StyledElement { get; } - public IXamlType IStyledElement { get; } public IXamlType NameScope { get; } public IXamlMethod NameScopeSetNameScope { get; } public IXamlType INameScope { get; } @@ -117,7 +116,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers XamlIlTypes = cfg.WellKnownTypes; AvaloniaObject = cfg.TypeSystem.GetType("Avalonia.AvaloniaObject"); - IAvaloniaObject = cfg.TypeSystem.GetType("Avalonia.IAvaloniaObject"); AvaloniaObjectExtensions = cfg.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions"); AvaloniaProperty = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty"); AvaloniaPropertyT = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty`1"); @@ -139,12 +137,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute"); MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute"); OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On"); - AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject, + AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, AvaloniaObject, AvaloniaProperty, IBinding, cfg.WellKnownTypes.Object); UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType"); StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement"); - IStyledElement = cfg.TypeSystem.GetType("Avalonia.IStyledElement"); + StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement"); INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope"); INameScopeRegister = INameScope.GetMethod( new FindMethodMethodSignature("Register", XamlIlTypes.Void, @@ -223,7 +221,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers StyledElementClassesProperty = StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes)); ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions") - .FindMethod( "BindClass", IDisposable, false, IStyledElement, + .FindMethod( "BindClass", IDisposable, false, StyledElement, cfg.WellKnownTypes.String, IBinding, cfg.WellKnownTypes.Object); @@ -266,5 +264,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration)); return rv; } + + public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstGroupTransformationContext ctx) + { + if (ctx.TryGetItem(out var rv)) + return rv; + ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration)); + return rv; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs new file mode 100644 index 0000000000..d031a6086b --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs @@ -0,0 +1,21 @@ +using XamlX; +using XamlX.Ast; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; + +internal class XamlDocumentParseException : XamlParseException +{ + public string FilePath { get; } + + public XamlDocumentParseException(string path, XamlParseException parseException) + : base(parseException.Message, parseException.LineNumber, parseException.LinePosition) + { + FilePath = path; + } + + public XamlDocumentParseException(string path, string message, IXamlLineInfo lineInfo) + : base(message, lineInfo.Line, lineInfo.Position) + { + FilePath = path; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs new file mode 100644 index 0000000000..d5d452a9f3 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs @@ -0,0 +1,40 @@ +using System; +using XamlX.Ast; +using XamlX.IL; +using XamlX.TypeSystem; +#nullable enable + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; + +internal class XamlDocumentResource : IXamlDocumentResource +{ + public XamlDocumentResource( + XamlDocument xamlDocument, + string? uri, + IFileSource? fileSource, + IXamlType? classType, + IXamlTypeBuilder typeBuilder, + IXamlMethodBuilder populateMethod, + IXamlMethodBuilder? buildMethod) + { + XamlDocument = xamlDocument; + Uri = uri; + FileSource = fileSource; + ClassType = classType; + TypeBuilder = typeBuilder; + PopulateMethod = populateMethod; + BuildMethod = buildMethod; + } + + public XamlDocument XamlDocument { get; } + public string? Uri { get; } + public IFileSource? FileSource { get; } + + public IXamlType? ClassType { get; } + public IXamlTypeBuilder TypeBuilder { get; } + public IXamlMethodBuilder PopulateMethod { get; } + public IXamlMethodBuilder? BuildMethod { get; } + + IXamlMethod? IXamlDocumentResource.BuildMethod => BuildMethod; + IXamlMethod IXamlDocumentResource.PopulateMethod => PopulateMethod; +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 6c9d510ba0..264361e743 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml; using Avalonia.Markup.Xaml.Parsers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using Avalonia.Utilities; -using XamlX; using XamlX.Ast; using XamlX.Transform; using XamlX.Transform.Transformers; @@ -67,10 +65,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions string propertyName, IXamlAstTypeReference selectorTypeReference, IXamlLineInfo lineInfo) { XamlAstNamePropertyReference forgedReference; - - var parser = new PropertyParser(); - - var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan())); + + var parsedPropertyName = PropertyParser.Parse(new CharacterReader(propertyName.AsSpan())); if(parsedPropertyName.owner == null) forgedReference = new XamlAstNamePropertyReference(lineInfo, selectorTypeReference, propertyName, selectorTypeReference); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index cd3682c615..5e498f8bcc 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit cd3682c61577a3518de765f7938295d98ff9808c +Subproject commit 5e498f8bcca403a34aff5efc825cbb4e12b7fa8e diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index b5ba49ce2c..c59542c0f3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -6,10 +6,9 @@ $(DefineConstants);RUNTIME_XAML_CECIL False false - CS1591 + $(NoWarn);CS1591 - @@ -43,6 +42,7 @@ + @@ -68,12 +68,9 @@ + - - - - diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 578af64abb..0b6a2c522f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,7 +1,8 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using Avalonia.Platform; - +#nullable enable namespace Avalonia.Markup.Xaml { /// @@ -11,7 +12,7 @@ namespace Avalonia.Markup.Xaml { public interface IRuntimeXamlLoader { - object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration); + object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration); } /// @@ -23,6 +24,17 @@ namespace Avalonia.Markup.Xaml throw new XamlLoadException( $"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource"); } + + /// + /// Loads the XAML into a Avalonia component. + /// + /// The parent's service provider. + /// The object to load the XAML into. + public static void Load(IServiceProvider? sp, object obj) + { + throw new XamlLoadException( + $"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource"); + } /// /// Loads XAML from a URI. @@ -32,9 +44,26 @@ namespace Avalonia.Markup.Xaml /// A base URI to use if is relative. /// /// The loaded object. - public static object Load(Uri uri, Uri baseUri = null) + [RequiresUnreferencedCode(TrimmingMessages.AvaloniaXamlLoaderRequiresUnreferenceCodeMessage)] + public static object Load(Uri uri, Uri? baseUri = null) { - Contract.Requires(uri != null); + return Load(null, uri, baseUri); + } + + /// + /// Loads XAML from a URI. + /// + /// The parent's service provider. + /// The URI of the XAML file. + /// + /// A base URI to use if is relative. + /// + /// The loaded object. + [RequiresUnreferencedCode(TrimmingMessages.AvaloniaXamlLoaderRequiresUnreferenceCodeMessage)] + public static object Load(IServiceProvider? sp, Uri uri, Uri? baseUri = null) + { + if (uri is null) + throw new ArgumentNullException(nameof(uri)); var assetLocator = AvaloniaLocator.Current.GetService(); @@ -44,17 +73,31 @@ namespace Avalonia.Markup.Xaml "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); } + var absoluteUri = uri.IsAbsoluteUri + ? uri + : new Uri(baseUri ?? throw new InvalidOperationException("Cannot load relative Uri when BaseUri is null"), uri); + var compiledLoader = assetLocator.GetAssembly(uri, baseUri) ?.GetType("CompiledAvaloniaXaml.!XamlLoader") - ?.GetMethod("TryLoad", new[] {typeof(string)}); + ?.GetMethod("TryLoad", new[] { typeof(System.IServiceProvider), typeof(string) }); if (compiledLoader != null) { - var uriString = (!uri.IsAbsoluteUri && baseUri != null ? new Uri(baseUri, uri) : uri) - .ToString(); - var compiledResult = compiledLoader.Invoke(null, new object[] {uriString}); + var compiledResult = compiledLoader.Invoke(null, new object?[] { sp, absoluteUri.ToString()}); if (compiledResult != null) return compiledResult; } + else + { + compiledLoader = assetLocator.GetAssembly(uri, baseUri) + ?.GetType("CompiledAvaloniaXaml.!XamlLoader") + ?.GetMethod("TryLoad", new[] {typeof(string)}); + if (compiledLoader != null) + { + var compiledResult = compiledLoader.Invoke(null, new object?[] {absoluteUri.ToString()}); + if (compiledResult != null) + return compiledResult; + } + } // This is intended for unit-tests only var runtimeLoader = AvaloniaLocator.Current.GetService(); @@ -63,12 +106,9 @@ namespace Avalonia.Markup.Xaml var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); using (var stream = asset.stream) { - var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri); - return runtimeLoader.Load(stream, new RuntimeXamlLoaderConfiguration - { - LocalAssembly = asset.assembly, - BaseUri = absoluteUri - }); + var document = new RuntimeXamlLoaderDocument(absoluteUri, stream) { ServiceProvider = sp }; + var configuration = new RuntimeXamlLoaderConfiguration { LocalAssembly = asset.assembly }; + return runtimeLoader.Load(document, configuration); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index 45ca1c4adc..5b660af887 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -1,9 +1,9 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Controls; using Avalonia.Logging; -using Avalonia.Markup.Parsers; using Avalonia.Markup.Xaml.Parsers; using Avalonia.Markup.Xaml.Templates; using Avalonia.Styling; @@ -11,6 +11,7 @@ using Avalonia.Utilities; namespace Avalonia.Markup.Xaml.Converters { + [RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)] public class AvaloniaPropertyTypeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) @@ -21,8 +22,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { var registry = AvaloniaPropertyRegistry.Instance; - var parser = new PropertyParser(); - var (ns, owner, propertyName) = parser.Parse(new CharacterReader(((string)value).AsSpan())); + var (ns, owner, propertyName) = PropertyParser.Parse(new CharacterReader(((string)value).AsSpan())); var ownerType = TryResolveOwnerByName(context, ns, owner); var targetType = context.GetFirstParent()?.TargetType ?? context.GetFirstParent -"; - - using (StyledWindow(assets: ("test:style.xaml", styleXaml))) - { - var xaml = @" +"), + new RuntimeXamlLoaderDocument(@" - + -"; - - var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); +") + }; + + using (StyledWindow()) + { + var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var window = Assert.IsType(compiled[1]); var border = window.FindControl("border"); var brush = (ISolidColorBrush)border.Background; @@ -284,13 +286,14 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions [Fact] public void DynamicResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File() { - var styleXaml = @" + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @" #ff506070 - -"; - - using (StyledWindow(assets: ("test:style.xaml", styleXaml))) - { - var xaml = @" +"), + new RuntimeXamlLoaderDocument(@" - +