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/Avalonia.sln b/Avalonia.sln index 34b5596119..bd83cde620 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,9 +224,9 @@ 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 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 b5a09b5fbd..b95b455ca4 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/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/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 3b29dbc726..4150b52946 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -109,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() { 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/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 f2e90bb6d7..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. @@ -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/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 82ed96d982..496052ad35 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; static Gestures() @@ -52,32 +52,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); } @@ -92,20 +92,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)); } } } @@ -119,9 +121,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()) @@ -131,13 +134,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 39edab3fc6..d185148894 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -62,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. @@ -323,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 @@ -380,7 +377,7 @@ namespace Avalonia.Layout if (DesiredSize != previousDesiredSize) { - this.GetVisualParent()?.ChildDesiredSizeChanged(this); + this.GetVisualParent()?.ChildDesiredSizeChanged(this); } } } @@ -423,7 +420,7 @@ namespace Avalonia.Layout IsMeasureValid = false; IsArrangeValid = false; - if (((ILayoutable)this).IsAttachedToVisualTree) + if (IsAttachedToVisualTree) { (VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this); InvalidateVisual(); @@ -448,7 +445,7 @@ namespace Avalonia.Layout } /// - void ILayoutable.ChildDesiredSizeChanged(ILayoutable control) + internal void ChildDesiredSizeChanged(Layoutable control) { if (!_measuring) { @@ -456,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. /// @@ -471,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) { @@ -494,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) { @@ -596,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); @@ -709,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); } @@ -778,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()) @@ -789,7 +786,7 @@ namespace Avalonia.Layout } /// - protected sealed override void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) + protected sealed override void OnVisualParentChanged(Visual? oldParent, Visual? newParent) { LayoutHelper.InvalidateSelfAndChildrenMeasure(this); 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/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/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 0117fe0713..986110b5e6 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,13 +124,18 @@ 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) - return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf)); + { + // 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; } @@ -142,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 38d9b34937..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); 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/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/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/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/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 187edd8335..6043175eee 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -25,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. @@ -45,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); @@ -73,7 +81,8 @@ namespace Avalonia private Styles? _styles; private bool _stylesApplied; private bool _themeApplied; - private ITemplatedControl? _templatedParent; + private bool _templatedParentThemeApplied; + private AvaloniaObject? _templatedParent; private bool _dataContextUpdating; private ControlTheme? _implicitTheme; @@ -233,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); @@ -251,7 +260,7 @@ namespace Avalonia /// /// Gets the styled element's logical children. /// - protected IAvaloniaList LogicalChildren + protected internal IAvaloniaList LogicalChildren { get { @@ -284,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. @@ -367,6 +376,12 @@ namespace Avalonia _themeApplied = true; } + if (!_templatedParentThemeApplied) + { + ApplyTemplatedParentControlTheme(); + _templatedParentThemeApplied = true; + } + if (!_stylesApplied) { ApplyStyles(this); @@ -451,7 +466,7 @@ namespace Avalonia InheritanceParent = parent as AvaloniaObject; } - Parent = (IStyledElement?)parent; + Parent = (StyledElement?)parent; if (_logicalRoot != null) { @@ -482,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 } @@ -493,14 +508,9 @@ 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; } void IStyleHost.StylesAdded(IReadOnlyList styles) @@ -610,26 +620,38 @@ namespace Avalonia base.OnPropertyChanged(change); if (change.Property == ThemeProperty) - { OnControlThemeChanged(); - _themeApplied = false; - } } private protected virtual void OnControlThemeChanged() { var values = GetValueStore(); values.BeginStyling(); - try { values.RemoveFrames(FrameType.Theme); } - finally { values.EndStyling(); } + + try + { + values.RemoveFrames(FrameType.Theme); + } + finally + { + values.EndStyling(); + _themeApplied = false; + } } internal virtual void OnTemplatedParentControlThemeChanged() { var values = GetValueStore(); values.BeginStyling(); - try { values.RemoveFrames(FrameType.TemplatedParentTheme); } - finally { values.EndStyling(); } + try + { + values.RemoveFrames(FrameType.TemplatedParentTheme); + } + finally + { + values.EndStyling(); + _templatedParentThemeApplied = false; + } } internal ControlTheme? GetEffectiveTheme() @@ -674,7 +696,7 @@ namespace Avalonia } } - private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) + private static void DataContextNotifying(AvaloniaObject o, bool updateStarted) { if (o is StyledElement element) { @@ -740,13 +762,13 @@ namespace Avalonia private void ApplyControlTheme() { - var theme = GetEffectiveTheme(); - - if (theme is not null) + if (GetEffectiveTheme() is { } theme) ApplyControlTheme(theme, FrameType.Theme); + } - if (TemplatedParent is StyledElement styleableParent && - styleableParent.GetEffectiveTheme() is { } parentTheme) + private void ApplyTemplatedParentControlTheme() + { + if ((TemplatedParent as StyledElement)?.GetEffectiveTheme() is { } parentTheme) { ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme); } @@ -790,6 +812,28 @@ namespace Avalonia ApplyStyle(child, host, type); } + private void ReevaluateImplicitTheme() + { + // We only need to check if the theme has changed when Theme isn't set (i.e. when we + // have an implicit theme). + if (Theme is not null) + return; + + // Refetch the implicit theme. + var oldImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme; + _implicitTheme = null; + GetEffectiveTheme(); + + var newImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme; + + // If the implicit theme has changed, detach the existing theme. + if (newImplicitTheme != oldImplicitTheme) + { + OnControlThemeChanged(); + _themeApplied = false; + } + } + private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) { if (this.GetLogicalParent() == null && !(this is ILogicalRoot)) @@ -808,6 +852,7 @@ namespace Avalonia { _logicalRoot = e.Root; + ReevaluateImplicitTheme(); ApplyStyling(); NotifyResourcesChanged(propagate: false); @@ -832,7 +877,6 @@ namespace Avalonia if (_logicalRoot != null) { _logicalRoot = null; - _implicitTheme = null; InvalidateStyles(recurse: false); OnDetachedFromLogicalTree(e); DetachedFromLogicalTree?.Invoke(this, e); diff --git a/src/Avalonia.Base/StyledElementExtensions.cs b/src/Avalonia.Base/StyledElementExtensions.cs index 0c5a5f7438..d530dae8d4 100644 --- a/src/Avalonia.Base/StyledElementExtensions.cs +++ b/src/Avalonia.Base/StyledElementExtensions.cs @@ -5,7 +5,7 @@ namespace Avalonia { public static class StyledElementExtensions { - public static IDisposable BindClass(this IStyledElement target, string className, IBinding source, object anchor) => + public static IDisposable BindClass(this StyledElement target, string className, IBinding source, object anchor) => ClassBindingManager.Bind(target, className, source, anchor); } } diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index d00a675d0f..019ed09c20 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -22,7 +22,7 @@ namespace Avalonia StyledPropertyMetadata metadata, bool inherits = false, Func? validate = null, - Action? notifying = null) + Action? notifying = null) : base(name, ownerType, metadata, inherits, validate, notifying) { } @@ -42,7 +42,7 @@ namespace Avalonia /// /// The type of the additional owner. /// The property. - public StyledProperty AddOwner() where TOwner : IAvaloniaObject + public StyledProperty AddOwner() where TOwner : AvaloniaObject { AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); return this; diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index 6fb2f79918..a281a7b7f6 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Avalonia.Data; using Avalonia.PropertyStore; @@ -30,7 +31,7 @@ namespace Avalonia StyledPropertyMetadata metadata, bool inherits = false, Func? validate = null, - Action? notifying = null) + Action? notifying = null) : base(name, ownerType, metadata, notifying) { _inherits = inherits; @@ -74,7 +75,7 @@ namespace Avalonia /// internal bool HasCoercion { get; private set; } - public TValue CoerceValue(IAvaloniaObject instance, TValue baseValue) + public TValue CoerceValue(AvaloniaObject instance, TValue baseValue) { var metadata = GetMetadata(instance.GetType()); @@ -114,7 +115,7 @@ namespace Avalonia /// /// The type. /// The default value. - public void OverrideDefaultValue(TValue defaultValue) where T : IAvaloniaObject + public void OverrideDefaultValue(TValue defaultValue) where T : AvaloniaObject { OverrideDefaultValue(typeof(T), defaultValue); } @@ -134,7 +135,7 @@ namespace Avalonia /// /// The type. /// The metadata. - public void OverrideMetadata(StyledPropertyMetadata metadata) where T : IAvaloniaObject + public void OverrideMetadata(StyledPropertyMetadata metadata) where T : AvaloniaObject { base.OverrideMetadata(typeof(T), metadata); } @@ -206,6 +207,7 @@ namespace Avalonia } /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] internal override IDisposable? RouteSetValue( AvaloniaObject target, object? value, diff --git a/src/Avalonia.Base/StyledPropertyMetadata`1.cs b/src/Avalonia.Base/StyledPropertyMetadata`1.cs index 0a38960228..c71973fde8 100644 --- a/src/Avalonia.Base/StyledPropertyMetadata`1.cs +++ b/src/Avalonia.Base/StyledPropertyMetadata`1.cs @@ -19,7 +19,7 @@ namespace Avalonia public StyledPropertyMetadata( Optional defaultValue = default, BindingMode defaultBindingMode = BindingMode.Default, - Func? coerce = null) + Func? coerce = null) : base(defaultBindingMode) { _defaultValue = defaultValue; @@ -34,7 +34,7 @@ namespace Avalonia /// /// Gets the value coercion callback, if any. /// - public Func? CoerceValue { get; private set; } + public Func? CoerceValue { get; private set; } object? IStyledPropertyMetadata.DefaultValue => DefaultValue; diff --git a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs index e07534237a..487198a861 100644 --- a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs @@ -39,7 +39,7 @@ namespace Avalonia.Styling.Activators /// /// The listener. /// - /// This method should not call . + /// This method should not call . /// void Subscribe(IStyleActivatorSink sink); diff --git a/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs b/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs index 388671f3f7..20a4c11f79 100644 --- a/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs @@ -7,13 +7,13 @@ namespace Avalonia.Styling.Activators /// internal class PropertyEqualsActivator : StyleActivatorBase, IObserver { - private readonly IStyleable _control; + private readonly StyledElement _control; private readonly AvaloniaProperty _property; private readonly object? _value; private IDisposable? _subscription; public PropertyEqualsActivator( - IStyleable control, + StyledElement control, AvaloniaProperty property, object? value) { diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index a6093bf026..400ba18530 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -37,13 +37,13 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var controlParent = ((ILogical)control).LogicalParent; if (controlParent != null) { - var parentMatch = _parent.Match((IStyleable)controlParent, parent, subscribe); + var parentMatch = _parent.Match((StyledElement)controlParent, parent, subscribe); if (parentMatch.Result == SelectorMatchResult.Sometimes) { diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 5fc900d2cb..22c8f61577 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -37,7 +37,7 @@ namespace Avalonia.Styling throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); } - internal SelectorMatchResult TryAttach(IStyleable target, FrameType type) + internal SelectorMatchResult TryAttach(StyledElement target, FrameType type) { Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme); @@ -46,7 +46,7 @@ namespace Avalonia.Styling if (TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey)) + if (HasSettersOrAnimations && TargetType.IsAssignableFrom(((IStyleable)target).StyleKey)) { Attach(target, null, type); return SelectorMatchResult.AlwaysThisType; diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 728dd08301..a056f3ba8b 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -35,7 +35,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var c = (ILogical)control; var descendantMatches = new OrActivatorBuilder(); @@ -46,7 +46,7 @@ namespace Avalonia.Styling if (c is IStyleable) { - var match = _parent.Match((IStyleable)c, parent, subscribe); + var match = _parent.Match((StyledElement)c, parent, subscribe); if (match.Result == SelectorMatchResult.Sometimes) { diff --git a/src/Avalonia.Base/Styling/ISetter.cs b/src/Avalonia.Base/Styling/ISetter.cs index 87ba6c5680..22af90b446 100644 --- a/src/Avalonia.Base/Styling/ISetter.cs +++ b/src/Avalonia.Base/Styling/ISetter.cs @@ -19,6 +19,6 @@ namespace Avalonia.Styling /// This method should return an which can be used to apply /// the setter to the specified control. /// - ISetterInstance Instance(IStyleInstance styleInstance, IStyleable target); + ISetterInstance Instance(IStyleInstance styleInstance, StyledElement target); } } diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index 8ee1f001b6..a6cc2c0f76 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -8,7 +8,7 @@ namespace Avalonia.Styling /// Interface for styleable elements. /// [NotClientImplementable] - public interface IStyleable : IAvaloniaObject, INamed + public interface IStyleable : INamed { /// /// Gets the list of classes for the control. @@ -23,6 +23,6 @@ namespace Avalonia.Styling /// /// Gets the template parent of this element if the control comes from a template. /// - ITemplatedControl? TemplatedParent { get; } + AvaloniaObject? TemplatedParent { get; } } } diff --git a/src/Avalonia.Base/Styling/ITemplatedControl.cs b/src/Avalonia.Base/Styling/ITemplatedControl.cs deleted file mode 100644 index 68989c8a5e..0000000000 --- a/src/Avalonia.Base/Styling/ITemplatedControl.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Avalonia.Metadata; - -namespace Avalonia.Styling -{ - [NotClientImplementable] - public interface ITemplatedControl : IAvaloniaObject - { - } -} diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index dd627a7db9..980bf12907 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -13,7 +13,7 @@ namespace Avalonia.Styling public override string ToString(Style? owner) => owner?.Parent?.ToString() ?? "^"; - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (parent is Style s && s.Selector is not null) { @@ -23,7 +23,7 @@ namespace Avalonia.Styling { if (theme.TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - return theme.TargetType.IsAssignableFrom(control.StyleKey) ? + return theme.TargetType.IsAssignableFrom(((IStyleable)control).StyleKey) ? SelectorMatch.AlwaysThisType : SelectorMatch.NeverThisType; } diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index 4491762b8f..f6b1288ac5 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -45,7 +45,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var innerResult = _argument.Match(control, parent, subscribe); diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index 9191d2eaa5..ccfc2c781d 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -48,7 +48,7 @@ namespace Avalonia.Styling public int Step { get; } public int Offset { get; } - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (!(control is ILogical logical)) { diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index 0c48b76ded..c5ef9a0b2b 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -66,7 +66,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var activators = new OrActivatorBuilder(); var neverThisInstance = false; diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 7d58bab4a3..0865a7a8b0 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -1,11 +1,10 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Styling.Activators; using Avalonia.Utilities; -#nullable enable - namespace Avalonia.Styling { /// @@ -74,7 +73,7 @@ namespace Avalonia.Styling } /// - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (subscribe) { @@ -92,6 +91,8 @@ namespace Avalonia.Styling protected override Selector? MovePrevious() => _previous; protected override Selector? MovePreviousOrParent() => _previous; + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)] + [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)] internal static bool Compare(Type propertyType, object? propertyValue, object? value) { if (propertyType == typeof(object) && diff --git a/src/Avalonia.Base/Styling/PropertySetterInstance.cs b/src/Avalonia.Base/Styling/PropertySetterInstance.cs index 9028224cc1..68a9b8aafe 100644 --- a/src/Avalonia.Base/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterInstance.cs @@ -13,7 +13,7 @@ namespace Avalonia.Styling internal class PropertySetterInstance : SingleSubscriberObservableBase>, ISetterInstance { - private readonly IStyleable _target; + private readonly StyledElement _target; private readonly StyledPropertyBase? _styledProperty; private readonly DirectPropertyBase? _directProperty; private readonly T _value; @@ -21,7 +21,7 @@ namespace Avalonia.Styling private State _state; public PropertySetterInstance( - IStyleable target, + StyledElement target, StyledPropertyBase property, T value) { @@ -31,7 +31,7 @@ namespace Avalonia.Styling } public PropertySetterInstance( - IStyleable target, + StyledElement target, DirectPropertyBase property, T value) { diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index 7f5ee48351..c83950f72d 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -41,7 +41,7 @@ namespace Avalonia.Styling /// or simply return an immediate result. /// /// A . - public SelectorMatch Match(IStyleable control, IStyle? parent = null, bool subscribe = true) + public SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true) { // First match the selector until a combinator is found. Selectors are stored from // right-to-left, so MatchUntilCombinator reverses this order because the type selector @@ -88,7 +88,7 @@ namespace Avalonia.Styling /// or simply return an immediate result. /// /// A . - protected abstract SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe); + protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe); /// /// Moves to the previous selector. @@ -127,7 +127,7 @@ namespace Avalonia.Styling } private static SelectorMatch MatchUntilCombinator( - IStyleable control, + StyledElement control, Selector start, IStyle? parent, bool subscribe, @@ -144,7 +144,7 @@ namespace Avalonia.Styling } private static SelectorMatchResult Match( - IStyleable control, + StyledElement control, Selector selector, IStyle? parent, bool subscribe, diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index 92b35c9300..b7b44a7dfe 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Animation; using Avalonia.Data; using Avalonia.Metadata; @@ -64,7 +65,8 @@ namespace Avalonia.Styling void IValueEntry.Unsubscribe() { } - ISetterInstance ISetter.Instance(IStyleInstance instance, IStyleable target) + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] + ISetterInstance ISetter.Instance(IStyleInstance instance, StyledElement target) { if (target is not AvaloniaObject ao) throw new InvalidOperationException("Don't know how to instance a style on this type."); @@ -119,7 +121,7 @@ namespace Avalonia.Styling } } - private ISetterInstance SetDirectValue(IStyleable target) + private ISetterInstance SetDirectValue(StyledElement target) { target.SetValue(Property!, Value); return _direct ??= new DirectPropertySetterInstance(); diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index 15d8a9fe2e..a5d89392e9 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -59,7 +59,7 @@ namespace Avalonia.Styling base.SetParent(parent); } - internal SelectorMatchResult TryAttach(IStyleable target, object? host, FrameType type) + internal SelectorMatchResult TryAttach(StyledElement target, object? host, FrameType type) { _ = target ?? throw new ArgumentNullException(nameof(target)); diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index 83fcf04d2f..e8fc40ca4c 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -92,7 +92,7 @@ namespace Avalonia.Styling return false; } - internal ValueFrame Attach(IStyleable target, IStyleActivator? activator, FrameType type) + internal ValueFrame Attach(StyledElement target, IStyleActivator? activator, FrameType type) { if (target is not AvaloniaObject ao) throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects."); diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs index 4985aa16c7..ca602167c0 100644 --- a/src/Avalonia.Base/Styling/StyleInstance.cs +++ b/src/Avalonia.Base/Styling/StyleInstance.cs @@ -70,6 +70,9 @@ namespace Avalonia.Styling _animationTrigger ??= new Subject(); foreach (var animation in _animations) animation.Apply(animatable, null, _animationTrigger); + + if (_activator is null) + _animationTrigger.OnNext(true); } } diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs index f22bbc0eae..1b1886335f 100644 --- a/src/Avalonia.Base/Styling/Styles.cs +++ b/src/Avalonia.Base/Styling/Styles.cs @@ -226,7 +226,7 @@ namespace Avalonia.Styling } } - internal SelectorMatchResult TryAttach(IStyleable target, object? host) + internal SelectorMatchResult TryAttach(StyledElement target, object? host) { var result = SelectorMatchResult.NeverThisType; diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index 82d934ee34..b253efc6d2 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -36,9 +36,9 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { - var templatedParent = control.TemplatedParent as IStyleable; + var templatedParent = control.TemplatedParent as StyledElement; if (templatedParent == null) { diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 4e225985d2..7883fd23ab 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -95,11 +95,11 @@ namespace Avalonia.Styling } /// - protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (TargetType != null) { - var controlType = control.StyleKey ?? control.GetType(); + var controlType = ((IStyleable)control).StyleKey ?? control.GetType(); if (IsConcreteType) { diff --git a/src/Avalonia.Base/Thickness.cs b/src/Avalonia.Base/Thickness.cs index da3a98088f..c8e6d7dfd2 100644 --- a/src/Avalonia.Base/Thickness.cs +++ b/src/Avalonia.Base/Thickness.cs @@ -277,7 +277,7 @@ namespace Avalonia /// The string representation of the thickness. public override string ToString() { - return $"{_left},{_top},{_right},{_bottom}"; + return FormattableString.Invariant($"{_left},{_top},{_right},{_bottom}"); } /// diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index b8692bb771..5235e23e08 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs @@ -4,6 +4,7 @@ using System.IO; using System.Runtime.Serialization; using System.Xml.Linq; using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Avalonia.Utilities { @@ -33,6 +34,7 @@ namespace Avalonia.Utilities return entries; } + [RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")] public static void Write(Stream stream, List entries) { new BinaryWriter(stream).Write(LastKnownVersion); @@ -43,6 +45,7 @@ namespace Avalonia.Utilities }); } + [RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")] public static byte[] Create(Dictionary data) { var sources = data.ToList(); diff --git a/src/Avalonia.Base/Utilities/StringBuilderCache.cs b/src/Avalonia.Base/Utilities/StringBuilderCache.cs index be8b24c848..cbbcf2b398 100644 --- a/src/Avalonia.Base/Utilities/StringBuilderCache.cs +++ b/src/Avalonia.Base/Utilities/StringBuilderCache.cs @@ -9,6 +9,7 @@ using System; using System.Text; namespace Avalonia.Utilities; +#nullable enable // Provide a cached reusable instance of stringbuilder per thread. internal static class StringBuilderCache diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index d29335d3d0..3c44dd63ce 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; @@ -125,6 +126,7 @@ namespace Avalonia.Utilities /// The culture to use. /// If successful, contains the convert value. /// True if the cast was successful, otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] public static bool TryConvert(Type to, object? value, CultureInfo? culture, out object? result) { if (value == null) @@ -150,7 +152,7 @@ namespace Avalonia.Utilities if (toUnderl == typeof(string)) { - result = Convert.ToString(value, culture); + result = Convert.ToString(value, culture)!; return true; } @@ -244,6 +246,7 @@ namespace Avalonia.Utilities /// The value to convert. /// If successful, contains the converted value. /// True if the convert was successful, otherwise false. + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static bool TryConvertImplicit(Type to, object? value, out object? result) { if (value == null) @@ -306,6 +309,7 @@ namespace Avalonia.Utilities /// The type to convert to.. /// The culture to use. /// A value of . + [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] public static object? ConvertOrDefault(object? value, Type type, CultureInfo culture) { return TryConvert(type, value, culture, out var result) ? result : Default(type); @@ -318,11 +322,13 @@ namespace Avalonia.Utilities /// The value to convert. /// The type to convert to. /// A value of . + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static object? ConvertImplicitOrDefault(object? value, Type type) { return TryConvertImplicit(type, value, out var result) ? result : Default(type); } + [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static T ConvertImplicit(object value) { if (TryConvertImplicit(typeof(T), value, out var result)) @@ -339,6 +345,7 @@ namespace Avalonia.Utilities /// /// The type. /// The default value. + [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "We don't care about public ctors for the value types, and always return null for the ref types.")] public static object? Default(Type type) { if (type.IsValueType) @@ -391,7 +398,9 @@ namespace Avalonia.Utilities return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } - private static MethodInfo? FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType) + private static MethodInfo? FindTypeConversionOperatorMethod( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type fromType, + Type toType, OperatorType operatorType) { const string implicitName = "op_Implicit"; const string explicitName = "op_Explicit"; diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index 4605aba800..020ba7a6d9 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -20,7 +21,8 @@ namespace Avalonia.Utilities /// The event source. /// The name of the event. /// The subscriber. - public static void Subscribe(TTarget target, string eventName, EventHandler subscriber) + public static void Subscribe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] TTarget, TEventArgs, TSubscriber>( + TTarget target, string eventName, EventHandler subscriber) where TEventArgs : EventArgs where TSubscriber : class { _ = target ?? throw new ArgumentNullException(nameof(target)); @@ -92,7 +94,9 @@ namespace Avalonia.Utilities private static Dictionary s_Callers = new Dictionary(); - public Subscription(SubscriptionDic sdic, Type targetType, object target, string eventName) + public Subscription(SubscriptionDic sdic, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType, + object target, string eventName) { _sdic = sdic; _target = target; diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 2bb4a29b3c..7694119589 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -28,7 +28,7 @@ namespace Avalonia /// extension methods defined in . /// [UsableDuringInitialization] - public class Visual : StyledElement, IVisual + public class Visual : StyledElement { /// /// Defines the property. @@ -90,10 +90,10 @@ namespace Avalonia AvaloniaProperty.Register(nameof(RenderTransformOrigin), defaultValue: RelativePoint.Center); /// - /// Defines the property. + /// Defines the property. /// - public static readonly DirectProperty VisualParentProperty = - AvaloniaProperty.RegisterDirect(nameof(IVisual.VisualParent), o => o._visualParent); + public static readonly DirectProperty VisualParentProperty = + AvaloniaProperty.RegisterDirect(nameof(VisualParent), o => o._visualParent); /// /// Defines the property. @@ -109,7 +109,7 @@ namespace Avalonia private Rect _bounds; private TransformedBounds? _transformedBounds; private IRenderRoot? _visualRoot; - private IVisual? _visualParent; + private Visual? _visualParent; private bool _hasMirrorTransform; private TargetWeakEventSubscriber? _affectsRenderWeakSubscriber; @@ -137,7 +137,7 @@ namespace Avalonia // Disable transitions until we're added to the visual tree. DisableTransitions(); - var visualChildren = new AvaloniaList(); + var visualChildren = new AvaloniaList(); visualChildren.ResetBehavior = ResetBehavior.Remove; visualChildren.Validate = visual => ValidateVisualChild(visual); visualChildren.CollectionChanged += VisualChildrenChanged; @@ -193,7 +193,7 @@ namespace Avalonia { get { - IVisual? node = this; + Visual? node = this; while (node != null) { @@ -280,7 +280,7 @@ namespace Avalonia /// /// Gets the control's child visuals. /// - protected IAvaloniaList VisualChildren + protected internal IAvaloniaList VisualChildren { get; private set; @@ -289,7 +289,7 @@ namespace Avalonia /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// - protected IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot); + protected internal IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot); internal CompositionDrawListVisual? CompositionVisual { get; private set; } internal CompositionVisual? ChildCompositionVisual { get; set; } @@ -299,28 +299,12 @@ namespace Avalonia /// /// Gets a value indicating whether this control is attached to a visual root. /// - bool IVisual.IsAttachedToVisualTree => VisualRoot != null; - - /// - /// Gets the control's child controls. - /// - IAvaloniaReadOnlyList IVisual.VisualChildren => VisualChildren; + internal bool IsAttachedToVisualTree => VisualRoot != null; /// /// Gets the control's parent visual. /// - IVisual? IVisual.VisualParent => _visualParent; - - /// - /// Gets the root of the visual tree, if the control is attached to a visual tree. - /// - IRenderRoot? IVisual.VisualRoot => VisualRoot; - - TransformedBounds? IVisual.TransformedBounds - { - get { return _transformedBounds; } - set { SetAndRaise(TransformedBoundsProperty, ref _transformedBounds, value); } - } + internal Visual? VisualParent => _visualParent; /// /// Invalidates the visual and queues a repaint. @@ -433,8 +417,9 @@ namespace Avalonia OnAttachedToVisualTree(e); AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); + _visualRoot.Renderer?.RecalculateChildren(_visualParent!); - if (ZIndex != 0 && this.GetVisualParent() is Visual parent) + if (ZIndex != 0 && VisualParent is Visual parent) parent.HasNonUniformZIndexChildren = true; var visualChildren = VisualChildren; @@ -467,6 +452,11 @@ namespace Avalonia return CompositionVisual; } + internal void SetTransformedBounds(TransformedBounds? value) + { + SetAndRaise(TransformedBoundsProperty, ref _transformedBounds, value); + } + /// /// Calls the method /// for this control and all of its visual descendants. @@ -531,13 +521,9 @@ namespace Avalonia /// /// The old visual parent. /// The new visual parent. - protected virtual void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) + protected virtual void OnVisualParentChanged(Visual? oldParent, Visual? newParent) { - RaisePropertyChanged( - VisualParentProperty, - new Optional(oldParent), - new BindingValue(newParent), - BindingPriority.LocalValue); + RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue); } internal override ParametrizedLogger? GetBindingWarningLogger( @@ -591,7 +577,7 @@ namespace Avalonia /// Ensures a visual child is not null and not already parented. /// /// The visual child. - private static void ValidateVisualChild(IVisual c) + private static void ValidateVisualChild(Visual c) { if (c == null) { @@ -610,7 +596,7 @@ namespace Avalonia /// The event args. private static void ZIndexChanged(AvaloniaPropertyChangedEventArgs e) { - var sender = e.Sender as IVisual; + var sender = e.Sender as Visual; var parent = sender?.VisualParent; if (sender?.ZIndex != 0 && parent is Visual parentVisual) parentVisual.HasNonUniformZIndexChildren = true; diff --git a/src/Avalonia.Base/VisualExtensions.cs b/src/Avalonia.Base/VisualExtensions.cs index 7a5bbb105c..e8dc5465d6 100644 --- a/src/Avalonia.Base/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualExtensions.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia { /// - /// Extension methods for . + /// Extension methods for . /// public static class VisualExtensions { @@ -14,12 +14,12 @@ namespace Avalonia /// The visual. /// The point in screen coordinates. /// The point in client coordinates. - public static Point PointToClient(this IVisual visual, PixelPoint point) + public static Point PointToClient(this Visual visual, PixelPoint point) { var root = visual.VisualRoot ?? - throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual)); + throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual)); var rootPoint = root.PointToClient(point); - return root.TranslatePoint(rootPoint, visual)!.Value; + return ((Visual)root).TranslatePoint(rootPoint, visual)!.Value; } /// @@ -28,12 +28,12 @@ namespace Avalonia /// The visual. /// The point in client coordinates. /// The point in screen coordinates. - public static PixelPoint PointToScreen(this IVisual visual, Point point) + public static PixelPoint PointToScreen(this Visual visual, Point point) { var root = visual.VisualRoot ?? throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual)); - var p = visual.TranslatePoint(point, root); - return visual.VisualRoot.PointToScreen(p!.Value); + var p = visual.TranslatePoint(point, (Visual)root); + return root.PointToScreen(p!.Value); } /// @@ -46,7 +46,7 @@ namespace Avalonia /// A containing the transform or null if the visuals don't share a /// common ancestor. /// - public static Matrix? TransformToVisual(this IVisual from, IVisual to) + public static Matrix? TransformToVisual(this Visual from, Visual to) { var common = from.FindCommonVisualAncestor(to); @@ -76,7 +76,7 @@ namespace Avalonia /// A point value, now relative to the target visual rather than this source element, or null if the /// two elements have no common ancestor. /// - public static Point? TranslatePoint(this IVisual visual, Point point, IVisual relativeTo) + public static Point? TranslatePoint(this Visual visual, Point point, Visual relativeTo) { var transform = visual.TransformToVisual(relativeTo); @@ -94,10 +94,10 @@ namespace Avalonia /// The ancestor visual. /// The visual. /// The transform. - private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual) + private static Matrix GetOffsetFrom(Visual ancestor, Visual visual) { var result = Matrix.Identity; - IVisual? v = visual; + Visual? v = visual; while (v != ancestor) { diff --git a/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs b/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs index aec093c574..9c33b1ffb9 100644 --- a/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs +++ b/src/Avalonia.Base/VisualTree/IHostedVisualTreeRoot.cs @@ -11,6 +11,6 @@ namespace Avalonia.VisualTree /// /// The visual tree host. /// - IVisual? Host { get; } + Visual? Host { get; } } } diff --git a/src/Avalonia.Base/VisualTree/IVisual.cs b/src/Avalonia.Base/VisualTree/IVisual.cs deleted file mode 100644 index fdd2d187b8..0000000000 --- a/src/Avalonia.Base/VisualTree/IVisual.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using Avalonia.Collections; -using Avalonia.Media; -using Avalonia.Metadata; -using Avalonia.Rendering; - -namespace Avalonia.VisualTree -{ - /// - /// Represents control that has a visual on-screen representation. - /// - /// - /// The interface defines the interface required for a renderer to - /// render a control. You should not usually need to reference this interface unless - /// you are writing a renderer; instead use the extension methods defined in - /// to traverse the visual tree. This interface is - /// implemented by . It should not be necessary to implement it - /// anywhere else. - /// - [NotClientImplementable] - public interface IVisual - { - /// - /// Raised when the control is attached to a rooted visual tree. - /// - event EventHandler? AttachedToVisualTree; - - /// - /// Raised when the control is detached from a rooted visual tree. - /// - event EventHandler? DetachedFromVisualTree; - - /// - /// Gets the bounds of the control relative to its parent. - /// - Rect Bounds { get; } - - /// - /// Gets the bounds of the control relative to the window, accounting for rendering transforms. - /// - TransformedBounds? TransformedBounds { get; set; } - - /// - /// Gets a value indicating whether the control should be clipped to its bounds. - /// - bool ClipToBounds { get; set; } - - /// - /// Gets or sets the geometry clip for this visual. - /// - Geometry? Clip { get; set; } - - /// - /// Gets a value indicating whether this control is attached to a visual root. - /// - bool IsAttachedToVisualTree { get; } - - /// - /// Gets a value indicating whether this control and all its parents are visible. - /// - bool IsEffectivelyVisible { get; } - - /// - /// Gets or sets a value indicating whether this control is visible. - /// - bool IsVisible { get; set; } - - /// - /// Gets or sets the opacity of the control. - /// - double Opacity { get; set; } - - /// - /// Gets or sets the opacity mask for the control. - /// - IBrush? OpacityMask { get; set; } - - /// - /// Gets a value indicating whether to apply mirror transform on this control. - /// - bool HasMirrorTransform { get; } - - /// - /// Gets a value indicating whether to sort children when rendering this control - /// - bool HasNonUniformZIndexChildren { get; } - - /// - /// Gets or sets the render transform of the control. - /// - ITransform? RenderTransform { get; set; } - - /// - /// Gets or sets the render transform origin of the control. - /// - RelativePoint RenderTransformOrigin { get; set; } - - /// - /// Gets the control's child visuals. - /// - IAvaloniaReadOnlyList VisualChildren { get; } - - /// - /// Gets the control's parent visual. - /// - IVisual? VisualParent { get; } - - /// - /// Gets the root of the visual tree, if the control is attached to a visual tree. - /// - IRenderRoot? VisualRoot { get; } - - /// - /// Gets or sets the Z index of the node. - /// - int ZIndex { get; set; } - - /// - /// Invalidates the visual and queues a repaint. - /// - void InvalidateVisual(); - - /// - /// Renders the control to a . - /// - /// The context. - void Render(DrawingContext context); - } -} diff --git a/src/Avalonia.Base/VisualTree/TransformedBounds.cs b/src/Avalonia.Base/VisualTree/TransformedBounds.cs index 42f93d8edb..c6371d3e28 100644 --- a/src/Avalonia.Base/VisualTree/TransformedBounds.cs +++ b/src/Avalonia.Base/VisualTree/TransformedBounds.cs @@ -77,6 +77,6 @@ namespace Avalonia.VisualTree return !left.Equals(right); } - public override string ToString() => $"Bounds: {Bounds} Clip: {Clip} Transform {Transform}"; + public override string ToString() => FormattableString.Invariant($"Bounds: {Bounds} Clip: {Clip} Transform {Transform}"); } } diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs index a1b8894604..b58db3b276 100644 --- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs @@ -19,9 +19,9 @@ namespace Avalonia.VisualTree /// The number of steps from the visual to the ancestor or -1 if /// is not a descendent of . /// - public static int CalculateDistanceFromAncestor(this IVisual visual, IVisual? ancestor) + public static int CalculateDistanceFromAncestor(this Visual visual, Visual? ancestor) { - IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); + Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); var result = 0; while (v != null && v != ancestor) @@ -41,9 +41,9 @@ namespace Avalonia.VisualTree /// /// The number of steps from the visual to the root. /// - public static int CalculateDistanceFromRoot(IVisual visual) + public static int CalculateDistanceFromRoot(Visual visual) { - IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); + Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); var result = 0; v = v?.VisualParent; @@ -64,18 +64,18 @@ namespace Avalonia.VisualTree /// The first visual. /// The second visual. /// The common ancestor, or null if not found. - public static IVisual? FindCommonVisualAncestor(this IVisual visual, IVisual target) + public static Visual? FindCommonVisualAncestor(this Visual visual, Visual target) { - IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); + Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); if (target is null) { return null; } - IVisual? t = target; + Visual? t = target; - void GoUpwards(ref IVisual? node, int count) + void GoUpwards(ref Visual? node, int count) { for (int i = 0; i < count; ++i) { @@ -104,8 +104,8 @@ namespace Avalonia.VisualTree while (v != null && t != null) { - IVisual? firstParent = v.VisualParent; - IVisual? secondParent = t.VisualParent; + Visual? firstParent = v.VisualParent; + Visual? secondParent = t.VisualParent; if (firstParent == secondParent) { @@ -120,13 +120,13 @@ namespace Avalonia.VisualTree } /// - /// Enumerates the ancestors of an in the visual tree. + /// Enumerates the ancestors of an in the visual tree. /// /// The visual. /// The visual's ancestors. - public static IEnumerable GetVisualAncestors(this IVisual visual) + public static IEnumerable GetVisualAncestors(this Visual visual) { - IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); + Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); v = v.VisualParent; @@ -144,14 +144,14 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First ancestor of given type. - public static T? FindAncestorOfType(this IVisual visual, bool includeSelf = false) where T : class + public static T? FindAncestorOfType(this Visual visual, bool includeSelf = false) where T : class { if (visual is null) { return null; } - IVisual? parent = includeSelf ? visual : visual.VisualParent; + Visual? parent = includeSelf ? visual : visual.VisualParent; while (parent != null) { @@ -173,7 +173,7 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First descendant of given type. - public static T? FindDescendantOfType(this IVisual visual, bool includeSelf = false) where T : class + public static T? FindDescendantOfType(this Visual visual, bool includeSelf = false) where T : class { if (visual is null) { @@ -189,11 +189,11 @@ namespace Avalonia.VisualTree } /// - /// Enumerates an and its ancestors in the visual tree. + /// Enumerates an and its ancestors in the visual tree. /// /// The visual. /// The visual and its ancestors. - public static IEnumerable GetSelfAndVisualAncestors(this IVisual visual) + public static IEnumerable GetSelfAndVisualAncestors(this Visual visual) { _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -211,7 +211,7 @@ namespace Avalonia.VisualTree /// The root visual to test. /// The point. /// The visual at the requested point. - public static IVisual? GetVisualAt(this IVisual visual, Point p) + public static Visual? GetVisualAt(this Visual visual, Point p) { _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -228,7 +228,7 @@ namespace Avalonia.VisualTree /// children will be excluded from the results. /// /// The visual at the requested point. - public static IVisual? GetVisualAt(this IVisual visual, Point p, Func filter) + public static Visual? GetVisualAt(this Visual visual, Point p, Func filter) { _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -239,7 +239,7 @@ namespace Avalonia.VisualTree return null; } - var rootPoint = visual.TranslatePoint(p, root); + var rootPoint = visual.TranslatePoint(p, (Visual)root); if (rootPoint.HasValue) { @@ -255,8 +255,8 @@ namespace Avalonia.VisualTree /// The root visual to test. /// The point. /// The visuals at the requested point. - public static IEnumerable GetVisualsAt( - this IVisual visual, + public static IEnumerable GetVisualsAt( + this Visual visual, Point p) { _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -274,10 +274,10 @@ namespace Avalonia.VisualTree /// children will be excluded from the results. /// /// The visuals at the requested point. - public static IEnumerable GetVisualsAt( - this IVisual visual, + public static IEnumerable GetVisualsAt( + this Visual visual, Point p, - Func filter) + Func filter) { _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -285,41 +285,41 @@ namespace Avalonia.VisualTree if (root is null) { - return Array.Empty(); + return Array.Empty(); } - var rootPoint = visual.TranslatePoint(p, root); + var rootPoint = visual.TranslatePoint(p, (Visual)root); if (rootPoint.HasValue) { return root.Renderer.HitTest(rootPoint.Value, visual, filter); } - return Enumerable.Empty(); + return Enumerable.Empty(); } /// - /// Enumerates the children of an in the visual tree. + /// Enumerates the children of an in the visual tree. /// /// The visual. /// The visual children. - public static IEnumerable GetVisualChildren(this IVisual visual) + public static IEnumerable GetVisualChildren(this Visual visual) { return visual.VisualChildren; } /// - /// Enumerates the descendants of an in the visual tree. + /// Enumerates the descendants of an in the visual tree. /// /// The visual. /// The visual's ancestors. - public static IEnumerable GetVisualDescendants(this IVisual visual) + public static IEnumerable GetVisualDescendants(this Visual visual) { - foreach (IVisual child in visual.VisualChildren) + foreach (Visual child in visual.VisualChildren) { yield return child; - foreach (IVisual descendant in child.GetVisualDescendants()) + foreach (Visual descendant in child.GetVisualDescendants()) { yield return descendant; } @@ -327,11 +327,11 @@ namespace Avalonia.VisualTree } /// - /// Enumerates an and its descendants in the visual tree. + /// Enumerates an and its descendants in the visual tree. /// /// The visual. /// The visual and its ancestors. - public static IEnumerable GetSelfAndVisualDescendants(this IVisual visual) + public static IEnumerable GetSelfAndVisualDescendants(this Visual visual) { yield return visual; @@ -342,36 +342,36 @@ namespace Avalonia.VisualTree } /// - /// Gets the visual parent of an . + /// Gets the visual parent of an . /// /// The visual. /// The parent, or null if the visual is unparented. - public static IVisual? GetVisualParent(this IVisual visual) + public static Visual? GetVisualParent(this Visual visual) { return visual.VisualParent; } /// - /// Gets the visual parent of an . + /// Gets the visual parent of an . /// /// The type of the visual parent. /// The visual. /// /// The parent, or null if the visual is unparented or its parent is not of type . /// - public static T? GetVisualParent(this IVisual visual) where T : class + public static T? GetVisualParent(this Visual visual) where T : class { return visual.VisualParent as T; } /// - /// Gets the root visual for an . + /// Gets the root visual for an . /// /// The visual. /// /// The root visual or null if the visual is not rooted. /// - public static IRenderRoot? GetVisualRoot(this IVisual visual) + public static IRenderRoot? GetVisualRoot(this Visual visual) { _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -379,7 +379,12 @@ namespace Avalonia.VisualTree } /// - /// Tests whether an is an ancestor of another visual. + /// Returns a value indicating whether this control is attached to a visual root. + /// + public static bool IsAttachedToVisualTree(this Visual visual) => visual.IsAttachedToVisualTree; + + /// + /// Tests whether an is an ancestor of another visual. /// /// The visual. /// The potential descendant. @@ -387,9 +392,9 @@ namespace Avalonia.VisualTree /// True if is an ancestor of ; /// otherwise false. /// - public static bool IsVisualAncestorOf(this IVisual visual, IVisual target) + public static bool IsVisualAncestorOf(this Visual visual, Visual target) { - IVisual? current = target?.VisualParent; + Visual? current = target?.VisualParent; while (current != null) { @@ -404,7 +409,7 @@ namespace Avalonia.VisualTree return false; } - public static IEnumerable SortByZIndex(this IEnumerable elements) + public static IEnumerable SortByZIndex(this IEnumerable elements) { return elements .Select((element, index) => new ZOrderElement @@ -417,14 +422,14 @@ namespace Avalonia.VisualTree .Select(x => x.Element!); } - private static T? FindDescendantOfTypeCore(IVisual visual) where T : class + private static T? FindDescendantOfTypeCore(Visual visual) where T : class { var visualChildren = visual.VisualChildren; var visualChildrenCount = visualChildren.Count; for (var i = 0; i < visualChildrenCount; i++) { - IVisual child = visualChildren[i]; + Visual child = visualChildren[i]; if (child is T result) { @@ -444,7 +449,7 @@ namespace Avalonia.VisualTree private class ZOrderElement : IComparable { - public IVisual? Element { get; set; } + public Visual? Element { get; set; } public int Index { get; set; } public int ZIndex { get; set; } diff --git a/src/Avalonia.Base/VisualTree/VisualLocator.cs b/src/Avalonia.Base/VisualTree/VisualLocator.cs index 940c87bafa..cb5ef4945c 100644 --- a/src/Avalonia.Base/VisualTree/VisualLocator.cs +++ b/src/Avalonia.Base/VisualTree/VisualLocator.cs @@ -6,18 +6,18 @@ namespace Avalonia.VisualTree { public class VisualLocator { - public static IObservable Track(IVisual relativeTo, int ancestorLevel, Type? ancestorType = null) + public static IObservable Track(Visual relativeTo, int ancestorLevel, Type? ancestorType = null) { return new VisualTracker(relativeTo, ancestorLevel, ancestorType); } - private class VisualTracker : LightweightObservableBase + private class VisualTracker : LightweightObservableBase { - private readonly IVisual _relativeTo; + private readonly Visual _relativeTo; private readonly int _ancestorLevel; private readonly Type? _ancestorType; - public VisualTracker(IVisual relativeTo, int ancestorLevel, Type? ancestorType) + public VisualTracker(Visual relativeTo, int ancestorLevel, Type? ancestorType) { _relativeTo = relativeTo; _ancestorLevel = ancestorLevel; @@ -36,14 +36,14 @@ namespace Avalonia.VisualTree _relativeTo.DetachedFromVisualTree -= AttachedDetached; } - protected override void Subscribed(IObserver observer, bool first) + protected override void Subscribed(IObserver observer, bool first) { observer.OnNext(GetResult()); } private void AttachedDetached(object? sender, VisualTreeAttachmentEventArgs e) => PublishNext(GetResult()); - private IVisual? GetResult() + private Visual? GetResult() { if (_relativeTo.IsAttachedToVisualTree) { diff --git a/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs b/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs index 57a9340be8..c0e8b1613f 100644 --- a/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs +++ b/src/Avalonia.Base/VisualTreeAttachmentEventArgs.cs @@ -1,6 +1,5 @@ using System; using Avalonia.Rendering; -using Avalonia.VisualTree; namespace Avalonia { @@ -15,7 +14,7 @@ namespace Avalonia /// /// The parent that the visual is being attached to or detached from. /// The root visual. - public VisualTreeAttachmentEventArgs(IVisual parent, IRenderRoot root) + public VisualTreeAttachmentEventArgs(Visual parent, IRenderRoot root) { Parent = parent ?? throw new ArgumentNullException(nameof(parent)); Root = root ?? throw new ArgumentNullException(nameof(root)); @@ -24,7 +23,7 @@ namespace Avalonia /// /// Gets the parent that the visual is being attached to or detached from. /// - public IVisual Parent { get; } + public Visual Parent { get; } /// /// Gets the root of the visual tree that the visual is being attached to or detached from. diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 74debed828..35b22f4f23 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -6,7 +6,7 @@ tools $(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL true - NU1605;CS8632 + $(NoWarn);NU1605;CS8632 @@ -55,6 +55,9 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) + + Markup\AssetLoader.cs + Markup/%(RecursiveDir)%(FileName)%(Extension) @@ -108,6 +111,7 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) + diff --git a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs index 43365346b3..a31c9a7516 100644 --- a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs +++ b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs @@ -5,6 +5,9 @@ namespace Avalonia.Build.Tasks InvalidXAML = 1, DuplicateXClass = 2, LegacyResmScheme = 3, + TransformError = 4, + EmitError = 4, + Loader = 5, Unknown = 9999 } diff --git a/src/Avalonia.Build.Tasks/Extensions.cs b/src/Avalonia.Build.Tasks/Extensions.cs index 46c12eaf3d..b0b7e0cc3d 100644 --- a/src/Avalonia.Build.Tasks/Extensions.cs +++ b/src/Avalonia.Build.Tasks/Extensions.cs @@ -5,17 +5,31 @@ namespace Avalonia.Build.Tasks { static class Extensions { - static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}"; + static string FormatErrorCode(BuildEngineErrorCode code) => FormattableString.Invariant($"AVLN:{(int)code:0000}"); - public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message) + public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, Exception ex, + int lineNumber = 0, int linePosition = 0) { - engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message, +#if DEBUG + LogError(engine, code, file, ex.ToString(), lineNumber, linePosition); +#else + LogError(engine, code, file, ex.Message, lineNumber, linePosition); +#endif + } + + public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message, + int lineNumber = 0, int linePosition = 0) + { + engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", + lineNumber, linePosition, lineNumber, linePosition, message, "", "Avalonia")); } - public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message) + public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message, + int lineNumber = 0, int linePosition = 0) { - engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message, + engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", + lineNumber, linePosition, lineNumber, linePosition, message, "", "Avalonia")); } diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index 38450ea2a2..fad6ad397b 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -67,7 +67,7 @@ namespace Avalonia.Build.Tasks { var src = new Source(r.ItemSpec, Root); - BuildEngine.LogMessage($"avares -> name:{src.Path}, path: {src.SystemPath}, size:{src.Size}, ItemSpec:{r.ItemSpec}", _reportImportance); + BuildEngine.LogMessage(FormattableString.Invariant($"avares -> name:{src.Path}, path: {src.SystemPath}, size:{src.Size}, ItemSpec:{r.ItemSpec}"), _reportImportance); return src; }).ToList(); diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 974f8485c0..a394d47904 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; +using Avalonia.Platform; using Microsoft.Build.Framework; using Mono.Cecil; using Mono.Cecil.Cil; @@ -94,7 +95,7 @@ namespace Avalonia.Build.Tasks } catch (Exception ex) { - engine.LogError(BuildEngineErrorCode.Unknown, "", ex.Message); + engine.LogError(BuildEngineErrorCode.Unknown, "", ex); return new CompileResult(false); } } @@ -134,6 +135,10 @@ namespace Avalonia.Build.Tasks engine.LogMessage("Debugging cancelled.", MessageImportance.Normal); } } + + // Some transformers might need to parse "avares://" Uri. + AssetLoader.RegisterResUriParsers(); + var asm = typeSystem.TargetAssemblyDefinition; var avares = new AvaloniaResources(asm, projectDirectory); if (avares.Resources.Count(CheckXamlName) == 0) @@ -191,7 +196,8 @@ namespace Avalonia.Build.Tasks var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); var createRootServiceProviderMethod = asm.MainModule.ImportReference( typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods - .First(x => x.Name == "CreateRootServiceProviderV2")); + .First(x => x.Name == "CreateRootServiceProviderV3")); + var serviceProviderType = createRootServiceProviderMethod.ReturnType; var loaderDispatcherDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!XamlLoader", TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object); @@ -207,11 +213,36 @@ namespace Avalonia.Build.Tasks MethodAttributes.Static | MethodAttributes.Public, asm.MainModule.TypeSystem.Object) { - Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)} + Parameters = + { + new ParameterDefinition(serviceProviderType), + new ParameterDefinition(asm.MainModule.TypeSystem.String) + }, + }; + var loaderDispatcherMethodOld = new MethodDefinition("TryLoad", + MethodAttributes.Static | MethodAttributes.Public, + asm.MainModule.TypeSystem.Object) + { + Parameters = + { + new ParameterDefinition(asm.MainModule.TypeSystem.String) + }, + Body = + { + Instructions = + { + Instruction.Create(OpCodes.Ldnull), + Instruction.Create(OpCodes.Ldarg_0), + Instruction.Create(OpCodes.Call, loaderDispatcherMethod), + Instruction.Create(OpCodes.Ret) + } + } }; loaderDispatcherDef.Methods.Add(loaderDispatcherMethod); + loaderDispatcherDef.Methods.Add(loaderDispatcherMethodOld); asm.MainModule.Types.Add(loaderDispatcherDef); + var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First( m => m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 3 && @@ -231,15 +262,14 @@ namespace Avalonia.Build.Tasks }); asm.MainModule.Types.Add(typeDef); var builder = typeSystem.CreateTypeBuilder(typeDef); - - var populateMethodsToTransform = new List<(MethodDefinition populateMethod, string resourceFilePath)>(); - - foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant())) + + IReadOnlyCollection parsedXamlDocuments = new List(); + foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x => x.FilePath.ToLowerInvariant())) { + engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance); + try { - engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance); - // StreamReader is needed here to handle BOM var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd(); var parsed = XDocumentXamlParser.Parse(xaml); @@ -276,9 +306,9 @@ namespace Avalonia.Build.Tasks compiler.Transform(parsed); + var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate"; - var buildName = classType == null ? "Build:" + res.Name : null; - + var buildName = classType == null ? "Build:" + res.Name : null; var classTypeDefinition = classType == null ? null : typeSystem.GetTypeReference(classType).Resolve(); @@ -286,11 +316,57 @@ namespace Avalonia.Build.Tasks var populateBuilder = classTypeDefinition == null ? builder : typeSystem.CreateTypeBuilder(classTypeDefinition); - compiler.Compile(parsed, - contextClass, + + ((List)parsedXamlDocuments).Add(new XamlDocumentResource( + parsed, res.Uri, res, classType, + populateBuilder, compiler.DefinePopulateMethod(populateBuilder, parsed, populateName, classTypeDefinition == null), - buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true), + buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true))); + } + catch (Exception e) + { + int lineNumber = 0, linePosition = 0; + if (e is XamlParseException xe) + { + lineNumber = xe.LineNumber; + linePosition = xe.LinePosition; + } + + engine.LogError(BuildEngineErrorCode.TransformError, res.FilePath, e, lineNumber, linePosition); + return false; + } + } + + try + { + compiler.TransformGroup(parsedXamlDocuments); + } + catch (XamlDocumentParseException e) + { + engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e, e.LineNumber, e.LinePosition); + } + catch (XamlParseException e) + { + engine.LogError(BuildEngineErrorCode.TransformError, "", e, e.LineNumber, e.LinePosition); + } + + foreach (var document in parsedXamlDocuments) + { + var parsed = document.XamlDocument; + var res = (IResource)document.FileSource; + var classType = document.ClassType; + var populateBuilder = document.TypeBuilder; + + try + { + var classTypeDefinition = + classType == null ? null : typeSystem.GetTypeReference(classType).Resolve(); + + compiler.Compile(parsed, + contextClass, + document.PopulateMethod, + document.BuildMethod, builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name, true), (closureName, closureBaseType) => @@ -300,23 +376,11 @@ namespace Avalonia.Build.Tasks res.Uri, res ); - var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve() - .Methods.First(m => m.Name == populateName); - - // Include populate method and all nested methods/closures used in the populate method, - // So we can replace style/resource includes in all of them. - populateMethodsToTransform.Add((compiledPopulateMethod, res.FilePath)); - populateMethodsToTransform.AddRange(compiledPopulateMethod.Body.Instructions - .Where(b => b.OpCode == OpCodes.Call || b.OpCode == OpCodes.Callvirt || b.OpCode == OpCodes.Ldftn) - .Select(b => b.Operand) - .OfType() - .Where(m => compiledPopulateMethod.DeclaringType.NestedTypes.Contains(m.DeclaringType)) - .Select(m => m.Resolve()) - .Where(m => m.HasBody) - .Select(m => (m, res.FilePath))); - if (classTypeDefinition != null) { + var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve() + .Methods.First(m => m.Name == document.PopulateMethod.Name); + var designLoaderFieldType = typeSystem .GetType("System.Action`1") .MakeGenericType(typeSystem.GetType("System.Object")); @@ -339,30 +403,42 @@ namespace Avalonia.Build.Tasks classTypeDefinition.Fields.Add(designLoaderField); const string TrampolineName = "!XamlIlPopulateTrampoline"; - var trampoline = new MethodDefinition(TrampolineName, - MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); - trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); - classTypeDefinition.Methods.Add(trampoline); + var trampolineMethodWithoutSP = new Lazy(() => CreateTrampolineMethod(false)); + var trampolineMethodWithSP = new Lazy(() => CreateTrampolineMethod(true)); + MethodDefinition CreateTrampolineMethod(bool hasSystemProviderArg) + { + var trampoline = new MethodDefinition(TrampolineName, + MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); + if (hasSystemProviderArg) + { + trampoline.Parameters.Add(new ParameterDefinition(serviceProviderType)); + } + trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); + + classTypeDefinition.Methods.Add(trampoline); - var regularStart = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod); + var regularStart = Instruction.Create(OpCodes.Nop); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - trampoline.Body.Instructions.Add(regularStart); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - CopyDebugDocument(trampoline, compiledPopulateMethod); + trampoline.Body.Instructions.Add(regularStart); + trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_0 : OpCodes.Ldnull)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod)); + trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + CopyDebugDocument(trampoline, compiledPopulateMethod); + return trampoline; + } var foundXamlLoader = false; - // Find AvaloniaXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this) - foreach (var method in classTypeDefinition.Methods - .Where(m => !m.Attributes.HasFlag(MethodAttributes.Static))) + // Find AvaloniaXamlLoader.Load(this) or AvaloniaXamlLoader.Load(sp, this) and replace it with !XamlIlPopulateTrampoline(this) + foreach (var method in classTypeDefinition.Methods.ToArray()) { var i = method.Body.Instructions; for (var c = 1; c < i.Count; c++) @@ -370,15 +446,11 @@ namespace Avalonia.Build.Tasks if (i[c].OpCode == OpCodes.Call) { var op = i[c].Operand as MethodReference; - - // TODO: Throw an error - // This usually happens when the same XAML resource was added twice for some weird reason - // We currently support it for dual-named default theme resources if (op != null && op.Name == TrampolineName) { - foundXamlLoader = true; - break; + throw new InvalidProgramException("Same XAML file was loaded twice." + + "Make sure there is no x:Class duplicates no files were added to the AvaloniaResource msbuild items group twice."); } if (op != null && op.Name == "Load" @@ -388,7 +460,20 @@ namespace Avalonia.Build.Tasks { if (MatchThisCall(i, c - 1)) { - i[c].Operand = trampoline; + i[c].Operand = trampolineMethodWithoutSP.Value; + foundXamlLoader = true; + } + } + if (op != null + && op.Name == "Load" + && op.Parameters.Count == 2 + && op.Parameters[0].ParameterType.FullName == "System.IServiceProvider" + && op.Parameters[1].ParameterType.FullName == "System.Object" + && op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader") + { + if (MatchThisCall(i, c - 1)) + { + i[c].Operand = trampolineMethodWithSP.Value; foundXamlLoader = true; } } @@ -405,7 +490,7 @@ namespace Avalonia.Build.Tasks { var i = ctors[0].Body.Instructions; var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret)); - i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline)); + i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampolineMethodWithoutSP.Value)); i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0)); } else @@ -417,30 +502,43 @@ namespace Avalonia.Build.Tasks } - if (buildName != null || classTypeDefinition != null) + if (document.BuildMethod != null || classTypeDefinition != null) { - var compiledBuildMethod = buildName == null ? + var compiledBuildMethod = document.BuildMethod == null ? null : typeSystem.GetTypeReference(builder).Resolve() - .Methods.First(m => m.Name == buildName); + .Methods.First(m => m.Name == document.BuildMethod?.Name); var parameterlessConstructor = compiledBuildMethod != null ? null : classTypeDefinition.GetConstructors().FirstOrDefault(c => c.IsPublic && !c.IsStatic && !c.HasParameters); + var constructorWithSp = compiledBuildMethod != null ? + null : + classTypeDefinition.GetConstructors().FirstOrDefault(c => + c.IsPublic && !c.IsStatic && c.Parameters.Count == 1 && c.Parameters[0].ParameterType.FullName == serviceProviderType.FullName); - if (compiledBuildMethod != null || parameterlessConstructor != null) + if (compiledBuildMethod != null || parameterlessConstructor != null || constructorWithSp != null) { var i = loaderDispatcherMethod.Body.Instructions; var nop = Instruction.Create(OpCodes.Nop); - i.Add(Instruction.Create(OpCodes.Ldarg_0)); + i.Add(Instruction.Create(OpCodes.Ldarg_1)); i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri)); i.Add(Instruction.Create(OpCodes.Ldc_I4, (int)StringComparison.OrdinalIgnoreCase)); i.Add(Instruction.Create(OpCodes.Call, stringEquals)); i.Add(Instruction.Create(OpCodes.Brfalse, nop)); if (parameterlessConstructor != null) + { i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor)); + } + else if (constructorWithSp != null) + { + i.Add(Instruction.Create(OpCodes.Ldarg_0)); + i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod)); + i.Add(Instruction.Create(OpCodes.Newobj, constructorWithSp)); + } else { + i.Add(Instruction.Create(OpCodes.Ldarg_0)); i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod)); i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod)); } @@ -448,6 +546,11 @@ namespace Avalonia.Build.Tasks i.Add(Instruction.Create(OpCodes.Ret)); i.Add(nop); } + else + { + engine.LogWarning(BuildEngineErrorCode.Loader, "", + $"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found"); + } } } @@ -459,22 +562,12 @@ namespace Avalonia.Build.Tasks lineNumber = xe.LineNumber; linePosition = xe.LinePosition; } - engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath, - lineNumber, linePosition, lineNumber, linePosition, - e.Message, "", "Avalonia")); + engine.LogError(BuildEngineErrorCode.EmitError, res.FilePath, e, lineNumber, linePosition); return false; } res.Remove(); } - foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform) - { - if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod)) - { - return false; - } - } - // Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds int dupeCounter = 1; foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name)) @@ -551,116 +644,5 @@ namespace Avalonia.Build.Tasks return true; } - - private static bool TransformXamlIncludes( - IBuildEngine engine, CecilTypeSystem typeSystem, - MethodDefinition populateMethod, string resourceFilePath, - MethodReference createRootServiceProviderMethod) - { - var asm = typeSystem.TargetAssemblyDefinition; - foreach (var instruction in populateMethod.Body.Instructions.ToArray()) - { - const string resolveStyleIncludeName = "ResolveStyleInclude"; - const string resolveResourceInclude = "ResolveResourceInclude"; - if (instruction.OpCode == OpCodes.Call - && instruction.Operand is MethodReference - { - Name: resolveStyleIncludeName or resolveResourceInclude, - DeclaringType: { Name: "XamlIlRuntimeHelpers" } - }) - { - int lineNumber = 0, linePosition = 0; - bool instructionsModified = false; - try - { - var assetSource = (string)instruction.Previous.Previous.Previous.Operand; - lineNumber = GetConstValue(instruction.Previous.Previous); - linePosition = GetConstValue(instruction.Previous); - - var index = populateMethod.Body.Instructions.IndexOf(instruction); - - assetSource = assetSource.Replace("avares://", ""); - - var assemblyNameSeparator = assetSource.IndexOf('/'); - var fileNameSeparator = assetSource.LastIndexOf('/'); - if (assemblyNameSeparator < 0 || fileNameSeparator < 0) - { - throw new InvalidProgramException( - $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path."); - } - - var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator); - var assetAssembly = typeSystem.FindAssembly(assetAssemblyName) - ?? throw new InvalidProgramException( - $"Unable to resolve assembly \"{assetAssemblyName}\""); - - var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.')); - if (assetAssembly.FindType(fileName) is { } type - && type.FindConstructor() is { } ctor) - { - var ctorMethod = - asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor)); - instructionsModified = true; - populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod); - } - else - { - var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources") - ?? throw new InvalidOperationException( - $"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly"); - - var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator); - var buildMethod = resources.FindMethod(m => m.Name == relativeName) - ?? throw new InvalidOperationException( - $"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly"); - - var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod)); - instructionsModified = true; - populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 1] = - Instruction.Create(OpCodes.Call, createRootServiceProviderMethod); - populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference); - } - } - catch (Exception e) - { - if (instructionsModified) - { - engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath, - lineNumber, linePosition, lineNumber, linePosition, - e.Message, "", "Avalonia")); - return false; - } - else - { - engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL", - resourceFilePath, - lineNumber, linePosition, lineNumber, linePosition, - e.Message, "", "Avalonia")); - } - } - - static int GetConstValue(Instruction instruction) - { - if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 }) - { - return instruction.OpCode.Code - Code.Ldc_I4_0; - } - if (instruction.Operand is not null) - { - return Convert.ToInt32(instruction.Operand); - } - - return 0; - } - } - } - - return true; - } } } diff --git a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj index e0790795c5..f3f8e4c82c 100644 --- a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj +++ b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index ec08e96d87..be320246b3 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -154,7 +154,7 @@ namespace Avalonia.Controls.Primitives /// /// The to round component values for. /// A new with rounded component values. - private HsvColor RoundComponentValues(HsvColor hsvColor) + private static HsvColor RoundComponentValues(HsvColor hsvColor) { return new HsvColor( Math.Round(hsvColor.A, 2, MidpointRounding.AwayFromZero), diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index f0ed89fb3a..2df46889a9 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -1146,7 +1146,7 @@ namespace Avalonia.Controls.Primitives }); } - private void FillPixelForBox( + private static void FillPixelForBox( double x, double y, Hsv baseHsv, diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 89f1afb1ac..977b1f5c84 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -329,7 +329,7 @@ namespace Avalonia.Controls /// The instance. /// The value to coerce. /// The coerced/validated value. - private static Color CoerceColor(IAvaloniaObject instance, Color value) + private static Color CoerceColor(AvaloniaObject instance, Color value) { if (instance is ColorView colorView) { @@ -345,7 +345,7 @@ namespace Avalonia.Controls /// The instance. /// The value to coerce. /// The coerced/validated value. - private static HsvColor CoerceHsvColor(IAvaloniaObject instance, HsvColor value) + private static HsvColor CoerceHsvColor(AvaloniaObject instance, HsvColor value) { if (instance is ColorView colorView) { diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 5b6fbfe09f..454678c64b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls /// /// Displays data in a customizable grid. /// - [TemplatePart(DATAGRID_elementBottomRightCornerHeaderName, typeof(IVisual))] + [TemplatePart(DATAGRID_elementBottomRightCornerHeaderName, typeof(Visual))] [TemplatePart(DATAGRID_elementColumnHeadersPresenterName, typeof(DataGridColumnHeadersPresenter))] [TemplatePart(DATAGRID_elementFrozenColumnScrollBarSpacerName, typeof(Control))] [TemplatePart(DATAGRID_elementHorizontalScrollbarName, typeof(ScrollBar))] @@ -91,7 +91,7 @@ namespace Avalonia.Controls private INotifyCollectionChanged _topLevelGroup; private ContentControl _clipboardContentControl; - private IVisual _bottomRightCorner; + private Visual _bottomRightCorner; private DataGridColumnHeadersPresenter _columnHeadersPresenter; private DataGridRowsPresenter _rowsPresenter; private ScrollBar _vScrollBar; @@ -122,7 +122,7 @@ namespace Avalonia.Controls private bool _executingLostFocusActions; private bool _flushCurrentCellChanged; private bool _focusEditingControl; - private IVisual _focusedObject; + private Visual _focusedObject; private byte _horizontalScrollChangesIgnored; private DataGridRow _focusedRow; private bool _ignoreNextScrollBarsLayout; @@ -566,13 +566,13 @@ namespace Avalonia.Controls set { SetValue(VerticalScrollBarVisibilityProperty, value); } } - public static readonly StyledProperty> DropLocationIndicatorTemplateProperty = - AvaloniaProperty.Register>(nameof(DropLocationIndicatorTemplate)); + public static readonly StyledProperty> DropLocationIndicatorTemplateProperty = + AvaloniaProperty.Register>(nameof(DropLocationIndicatorTemplate)); /// /// Gets or sets the template that is used when rendering the column headers. /// - public ITemplate DropLocationIndicatorTemplate + public ITemplate DropLocationIndicatorTemplate { get { return GetValue(DropLocationIndicatorTemplateProperty); } set { SetValue(DropLocationIndicatorTemplateProperty, value); } @@ -2514,7 +2514,7 @@ namespace Avalonia.Controls _topLeftCornerHeader = e.NameScope.Find(DATAGRID_elementTopLeftCornerHeaderName); EnsureTopLeftCornerHeader(); // EnsureTopLeftCornerHeader checks for a null _topLeftCornerHeader; _topRightCornerHeader = e.NameScope.Find(DATAGRID_elementTopRightCornerHeaderName); - _bottomRightCorner = e.NameScope.Find(DATAGRID_elementBottomRightCornerHeaderName); + _bottomRightCorner = e.NameScope.Find(DATAGRID_elementBottomRightCornerHeaderName); } /// @@ -3173,7 +3173,7 @@ namespace Avalonia.Controls if (EditingRow != null && EditingColumnIndex != -1 && !_executingLostFocusActions) { DataGridColumn editingColumn = ColumnsItemsInternal[EditingColumnIndex]; - IControl editingElement = editingColumn.GetCellContent(EditingRow); + Control editingElement = editingColumn.GetCellContent(EditingRow); if (editingElement != null && editingElement.ContainsChild(_focusedObject)) { Debug.Assert(_lostFocusActions != null); @@ -3913,7 +3913,7 @@ namespace Avalonia.Controls // Keep track of which row contains the newly focused element DataGridRow focusedRow = null; - IVisual focusedElement = e.Source as IVisual; + Visual focusedElement = e.Source as Visual; _focusedObject = focusedElement; while (focusedElement != null) { @@ -3966,7 +3966,7 @@ namespace Avalonia.Controls { bool focusLeftDataGrid = true; bool dataGridWillReceiveRoutedEvent = true; - IVisual focusedObject = FocusManager.Instance.Current; + Visual focusedObject = FocusManager.Instance.Current as Visual; while (focusedObject != null) { @@ -3979,7 +3979,7 @@ namespace Avalonia.Controls // Walk up the visual tree. If we hit the root, try using the framework element's // parent. We do this because Popups behave differently with respect to the visual tree, // and it could have a parent even if the VisualTreeHelper doesn't find it. - IVisual parent = focusedObject.GetVisualParent(); + Visual parent = focusedObject.GetVisualParent(); if (parent == null) { if (focusedObject is Control element) @@ -4592,7 +4592,7 @@ namespace Avalonia.Controls Debug.Assert(dataGridRow != null); Debug.Assert(dataGridCell != null); - IControl element = null; + Control element = null; DataGridBoundColumn dataGridBoundColumn = dataGridColumn as DataGridBoundColumn; if (isCellEdited) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index 97e247bdc6..ce74604f70 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -97,9 +97,9 @@ namespace Avalonia.Controls //TODO Rename //TODO Validation - protected sealed override IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding editBinding) + protected sealed override Control GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding editBinding) { - IControl element = GenerateEditingElementDirect(cell, dataItem); + Control element = GenerateEditingElementDirect(cell, dataItem); editBinding = null; if (Binding != null) @@ -110,7 +110,7 @@ namespace Avalonia.Controls return element; } - private static ICellEditBinding BindEditingElement(IAvaloniaObject target, AvaloniaProperty property, IBinding binding) + private static ICellEditBinding BindEditingElement(AvaloniaObject target, AvaloniaProperty property, IBinding binding) { var result = binding.Initiate(target, property, enableDataValidation: true); @@ -131,7 +131,7 @@ namespace Avalonia.Controls return null; } - protected abstract IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem); + protected abstract Control GenerateEditingElementDirect(DataGridCell cell, object dataItem); protected AvaloniaProperty BindingTarget { get; set; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index 9826c15598..6b1796e50b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -65,7 +65,7 @@ namespace Avalonia.Controls /// /// The previous, unedited value in the cell being edited. /// - protected override void CancelCellEdit(IControl editingElement, object uneditedValue) + protected override void CancelCellEdit(Control editingElement, object uneditedValue) { if (editingElement is CheckBox editingCheckBox) { @@ -85,7 +85,7 @@ namespace Avalonia.Controls /// /// A new control that is bound to the column's property value. /// - protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem) + protected override Control GenerateEditingElementDirect(DataGridCell cell, object dataItem) { var checkBox = new CheckBox { @@ -107,7 +107,7 @@ namespace Avalonia.Controls /// /// A new, read-only control that is bound to the column's property value. /// - protected override IControl GenerateElement(DataGridCell cell, object dataItem) + protected override Control GenerateElement(DataGridCell cell, object dataItem) { bool isEnabled = false; CheckBox checkBoxElement = new CheckBox(); @@ -148,7 +148,7 @@ namespace Avalonia.Controls /// /// The unedited value. /// - protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) + protected override object PrepareCellForEdit(Control editingElement, RoutedEventArgs editingEventArgs) { if (editingElement is CheckBox editingCheckBox) { @@ -218,7 +218,7 @@ namespace Avalonia.Controls /// Called by the DataGrid control when this column asks for its elements to be /// updated, because its CheckBoxContent or IsThreeState property changed. /// - protected internal override void RefreshCellContent(IControl element, string propertyName) + protected internal override void RefreshCellContent(Control element, string propertyName) { if (element == null) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 191c3fc5b1..d417c87746 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls private object _header; private IDataTemplate _headerTemplate; private DataGridColumnHeader _headerCell; - private IControl _editingElement; + private Control _editingElement; private ICellEditBinding _editBinding; private IBinding _clipboardContentBinding; private ControlTheme _cellTheme; @@ -636,7 +636,7 @@ namespace Avalonia.Controls return content; } - public IControl GetCellContent(DataGridRow dataGridRow) + public Control GetCellContent(DataGridRow dataGridRow) { Contract.Requires(dataGridRow != null); if (OwningGrid == null) @@ -648,13 +648,13 @@ namespace Avalonia.Controls DataGridCell dataGridCell = dataGridRow.Cells[Index]; if (dataGridCell != null) { - return dataGridCell.Content as IControl; + return dataGridCell.Content as Control; } } return null; } - public IControl GetCellContent(object dataItem) + public Control GetCellContent(object dataItem) { Contract.Requires(dataItem != null); if (OwningGrid == null) @@ -675,10 +675,10 @@ namespace Avalonia.Controls /// element contained in a column /// Column that contains the element, or null if not found /// - public static DataGridColumn GetColumnContainingElement(IControl element) + public static DataGridColumn GetColumnContainingElement(Control element) { // Walk up the tree to find the DataGridCell or DataGridColumnHeader that contains the element - IVisual parent = element; + Visual parent = element; while (parent != null) { if (parent is DataGridCell cell) @@ -731,7 +731,7 @@ namespace Avalonia.Controls /// /// The previous, unedited value in the cell being edited. /// - protected virtual void CancelCellEdit(IControl editingElement, object uneditedValue) + protected virtual void CancelCellEdit(Control editingElement, object uneditedValue) { } /// @@ -747,7 +747,7 @@ namespace Avalonia.Controls /// /// A new editing element that is bound to the column's property value. /// - protected abstract IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding); + protected abstract Control GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding); /// /// When overridden in a derived class, gets a read-only element that is bound to the column's @@ -762,7 +762,7 @@ namespace Avalonia.Controls /// /// A new, read-only element that is bound to the column's property value. /// - protected abstract IControl GenerateElement(DataGridCell cell, object dataItem); + protected abstract Control GenerateElement(DataGridCell cell, object dataItem); /// /// Called by a specific column type when one of its properties changed, @@ -786,7 +786,7 @@ namespace Avalonia.Controls /// /// The unedited value. /// - protected abstract object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs); + protected abstract object PrepareCellForEdit(Control editingElement, RoutedEventArgs editingEventArgs); /// /// Called by the DataGrid control when a column asked for its @@ -794,10 +794,10 @@ namespace Avalonia.Controls /// /// Indicates the element that needs to be refreshed /// Indicates which property changed and caused this call - protected internal virtual void RefreshCellContent(IControl element, string propertyName) + protected internal virtual void RefreshCellContent(Control element, string propertyName) { } - internal void CancelCellEditInternal(IControl editingElement, object uneditedValue) + internal void CancelCellEditInternal(Control editingElement, object uneditedValue) { CancelCellEdit(editingElement, uneditedValue); } @@ -908,12 +908,12 @@ namespace Avalonia.Controls SetWidthInternalNoCallback(CoerceWidth(Width)); } - internal IControl GenerateElementInternal(DataGridCell cell, object dataItem) + internal Control GenerateElementInternal(DataGridCell cell, object dataItem) { return GenerateElement(cell, dataItem); } - internal object PrepareCellForEditInternal(IControl editingElement, RoutedEventArgs editingEventArgs) + internal object PrepareCellForEditInternal(Control editingElement, RoutedEventArgs editingEventArgs) { var result = PrepareCellForEdit(editingElement, editingEventArgs); editingElement.Focus(); @@ -1062,7 +1062,7 @@ namespace Avalonia.Controls } //TODO Binding - internal IControl GenerateEditingElementInternal(DataGridCell cell, object dataItem) + internal Control GenerateEditingElementInternal(DataGridCell cell, object dataItem) { if (_editingElement == null) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 740e3516f6..6473c9c051 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -679,7 +679,7 @@ namespace Avalonia.Controls dragIndicator.PseudoClasses.Add(":dragIndicator"); - IControl dropLocationIndicator = OwningGrid.DropLocationIndicatorTemplate?.Build(); + Control dropLocationIndicator = OwningGrid.DropLocationIndicatorTemplate?.Build(); // If the user didn't style the dropLocationIndicator's Height, default to the column header's height if (dropLocationIndicator != null && double.IsNaN(dropLocationIndicator.Height) && dropLocationIndicator is Control element) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs index 52f0ad7537..703bc0d9c3 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs @@ -1437,7 +1437,7 @@ namespace Avalonia.Controls DataGridCell dataGridCell = dataGridRow.Cells[dataGridColumn.Index]; Debug.Assert(dataGridCell != null); - if (dataGridCell.Content is IControl element) + if (dataGridCell.Content is Control element) { dataGridColumn.RefreshCellContent(element, propertyName); } diff --git a/src/Avalonia.Controls.DataGrid/DataGridFillerColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridFillerColumn.cs index defcc523c6..6a71d40960 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridFillerColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridFillerColumn.cs @@ -51,18 +51,18 @@ namespace Avalonia.Controls return headerCell; } - protected override IControl GenerateElement(DataGridCell cell, object dataItem) + protected override Control GenerateElement(DataGridCell cell, object dataItem) { return null; } - protected override IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding editBinding) + protected override Control GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding editBinding) { editBinding = null; return null; } - protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) + protected override object PrepareCellForEdit(Control editingElement, RoutedEventArgs editingEventArgs) { return null; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index cd22934ac0..dfa6ea2e46 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -58,7 +58,7 @@ namespace Avalonia.Controls private bool _detailsLoaded; private bool _detailsVisibilityNotificationPending; - private IControl _detailsContent; + private Control _detailsContent; private IDisposable _detailsContentSizeSubscription; private DataGridDetailsPresenter _detailsElement; @@ -441,7 +441,7 @@ namespace Avalonia.Controls public static DataGridRow GetRowContainingElement(Control element) { // Walk up the tree to find the DataGridRow that contains the element - IVisual parent = element; + Visual parent = element; DataGridRow row = parent as DataGridRow; while ((parent != null) && (row == null)) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 17a7eab2e0..6c997f62c1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -2966,13 +2966,13 @@ namespace Avalonia.Controls } // detailsElement is the FrameworkElement created by the DetailsTemplate - internal void OnUnloadingRowDetails(DataGridRow row, IControl detailsElement) + internal void OnUnloadingRowDetails(DataGridRow row, Control detailsElement) { OnUnloadingRowDetails(new DataGridRowDetailsEventArgs(row, detailsElement)); } // detailsElement is the FrameworkElement created by the DetailsTemplate - internal void OnLoadingRowDetails(DataGridRow row, IControl detailsElement) + internal void OnLoadingRowDetails(DataGridRow row, Control detailsElement) { OnLoadingRowDetails(new DataGridRowDetailsEventArgs(row, detailsElement)); } diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index 0e754d5815..3ec78d6d6a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -56,13 +56,13 @@ namespace Avalonia.Controls set => SetAndRaise(CellEditingTemplateProperty, ref _cellEditingCellTemplate, value); } - private void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) + private static void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) { var oldValue = (IDataTemplate)e.OldValue; var value = (IDataTemplate)e.NewValue; } - protected override IControl GenerateElement(DataGridCell cell, object dataItem) + protected override Control GenerateElement(DataGridCell cell, object dataItem) { if(CellTemplate != null) { @@ -78,7 +78,7 @@ namespace Avalonia.Controls } } - protected override IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding) + protected override Control GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding) { binding = null; if(CellEditingTemplate != null) @@ -99,12 +99,12 @@ namespace Avalonia.Controls } } - protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) + protected override object PrepareCellForEdit(Control editingElement, RoutedEventArgs editingEventArgs) { return null; } - protected internal override void RefreshCellContent(IControl element, string propertyName) + protected internal override void RefreshCellContent(Control element, string propertyName) { var cell = element.Parent as DataGridCell; if(propertyName == nameof(CellTemplate) && cell is not null) diff --git a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs index 79247aa87f..82da1c3038 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs @@ -147,7 +147,7 @@ namespace Avalonia.Controls /// /// The element that the column displays for a cell in editing mode. /// The previous, unedited value in the cell being edited. - protected override void CancelCellEdit(IControl editingElement, object uneditedValue) + protected override void CancelCellEdit(Control editingElement, object uneditedValue) { if (editingElement is TextBox textBox) { @@ -162,7 +162,7 @@ namespace Avalonia.Controls /// The cell that will contain the generated element. /// The data item represented by the row that contains the intended cell. /// A new control that is bound to the column's property value. - protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem) + protected override Control GenerateEditingElementDirect(DataGridCell cell, object dataItem) { var textBox = new TextBox { @@ -184,7 +184,7 @@ namespace Avalonia.Controls /// The cell that will contain the generated element. /// The data item represented by the row that contains the intended cell. /// A new, read-only element that is bound to the column's property value. - protected override IControl GenerateElement(DataGridCell cell, object dataItem) + protected override Control GenerateElement(DataGridCell cell, object dataItem) { var textBlockElement = new TextBlock { @@ -210,7 +210,7 @@ namespace Avalonia.Controls /// The element that the column displays for a cell in editing mode. /// Information about the user gesture that is causing a cell to enter editing mode. /// The unedited value. - protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) + protected override object PrepareCellForEdit(Control editingElement, RoutedEventArgs editingEventArgs) { if (editingElement is TextBox textBox) { @@ -239,7 +239,7 @@ namespace Avalonia.Controls /// Called by the DataGrid control when this column asks for its elements to be /// updated, because a property changed. /// - protected internal override void RefreshCellContent(IControl element, string propertyName) + protected internal override void RefreshCellContent(Control element, string propertyName) { if (element == null) { diff --git a/src/Avalonia.Controls.DataGrid/EventArgs.cs b/src/Avalonia.Controls.DataGrid/EventArgs.cs index 7590a8ed61..da591d266f 100644 --- a/src/Avalonia.Controls.DataGrid/EventArgs.cs +++ b/src/Avalonia.Controls.DataGrid/EventArgs.cs @@ -345,7 +345,7 @@ namespace Avalonia.Controls /// /// UIElement to display at the insertion position. If null and Handled = true, then do not display an insertion indicator. /// - public IControl DropLocationIndicator + public Control DropLocationIndicator { get; set; @@ -542,7 +542,7 @@ namespace Avalonia.Controls /// /// The row that the event occurs for. /// The row details section as a framework element. - public DataGridRowDetailsEventArgs(DataGridRow row, IControl detailsElement) + public DataGridRowDetailsEventArgs(DataGridRow row, Control detailsElement) { Row = row; DetailsElement = detailsElement; @@ -551,7 +551,7 @@ namespace Avalonia.Controls /// /// Gets the row details section as a framework element. /// - public IControl DetailsElement + public Control DetailsElement { get; private set; diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs index 108dc8ded7..b34f52f47d 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.Primitives public sealed class DataGridColumnHeadersPresenter : Panel, IChildIndexProvider { private Control _dragIndicator; - private IControl _dropLocationIndicator; + private Control _dropLocationIndicator; private EventHandler _childIndexChanged; /// @@ -68,7 +68,7 @@ namespace Avalonia.Controls.Primitives /// /// The drop location indicator control. This value is null if no column is being dragged. /// - internal IControl DropLocationIndicator + internal Control DropLocationIndicator { get { diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index a3c0ed1d0c..fb4335f4de 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -6,8 +6,8 @@ M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z - M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z - M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z + M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z + M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z diff --git a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs index 2450708255..f4ba644ae6 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls.Utils /// Parent Visual /// Child Visual /// True if the parent element contains the child - internal static bool ContainsChild(this IVisual element, IVisual child) + internal static bool ContainsChild(this Visual element, Visual child) { if (element != null) { @@ -31,10 +31,10 @@ namespace Avalonia.Controls.Utils // Walk up the visual tree. If we hit the root, try using the framework element's // parent. We do this because Popups behave differently with respect to the visual tree, // and it could have a parent even if the VisualTreeHelper doesn't find it. - IVisual parent = child.GetVisualParent(); + Visual parent = child.GetVisualParent(); if (parent == null) { - if (child is IControl childElement) + if (child is Control childElement) { parent = childElement.Parent; } @@ -52,9 +52,9 @@ namespace Avalonia.Controls.Utils /// /// Parent Visual /// True if the currently focused element is within the visual tree of the parent - internal static bool ContainsFocusedElement(this IVisual element) + internal static bool ContainsFocusedElement(this Visual element) { - return (element == null) ? false : element.ContainsChild(FocusManager.Instance.Current); + return (element == null) ? false : element.ContainsChild(FocusManager.Instance.Current as Visual); } } } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 1dfad7dcc5..604f2369e7 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; using Avalonia.Controls.ApplicationLifetimes; @@ -162,13 +163,6 @@ namespace Avalonia.Controls return Self; } - /// - /// Specifies a windowing subsystem to use. - /// - /// The dll in which to look for subsystem. - /// An instance. - public TAppBuilder UseWindowingSubsystem(string dll) => UseWindowingSubsystem(GetInitializer(dll), dll.Replace("Avalonia.", string.Empty)); - /// /// Specifies a rendering subsystem to use. /// @@ -182,50 +176,8 @@ namespace Avalonia.Controls return Self; } - /// - /// Specifies a rendering subsystem to use. - /// - /// The dll in which to look for subsystem. - /// An instance. - public TAppBuilder UseRenderingSubsystem(string dll) => UseRenderingSubsystem(GetInitializer(dll)); - - static Action GetInitializer(string assemblyName) => () => - { - var assembly = Assembly.Load(new AssemblyName(assemblyName)); - var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform"; - var platformClassFullName = assemblyName + "." + platformClassName; - var platformClass = assembly.GetType(platformClassFullName); - var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes); - init!.Invoke(null, null); - }; - - public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); - protected virtual bool CheckSetup => true; - /// - /// Searches and initiates modules included with attribute. - /// - private void SetupAvaloniaModules() - { - var moduleInitializers = from assembly in AppDomain.CurrentDomain.GetAssemblies() - from attribute in assembly.GetCustomAttributes() - where string.IsNullOrEmpty(attribute.ForWindowingSubsystem) - || attribute.ForWindowingSubsystem == WindowingSubsystemName - where string.IsNullOrEmpty(attribute.ForRenderingSubsystem) - || attribute.ForRenderingSubsystem == RenderingSubsystemName - group attribute by attribute.Name into exports - select (from export in exports - orderby export.ForWindowingSubsystem.Length descending - orderby export.ForRenderingSubsystem.Length descending - select export).First().ModuleType into moduleType - select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors - where constructor.GetParameters().Length == 0 && !constructor.IsStatic - select constructor).Single() into constructor - select (Action)(() => constructor.Invoke(Array.Empty())); - Delegate.Combine(moduleInitializers.ToArray())!.DynamicInvoke(); - } - /// /// Configures platform-specific options /// diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index a027b8b650..e0d986f2b4 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -773,7 +773,7 @@ namespace Avalonia.Controls /// otherwise, false. protected bool HasFocus() { - IVisual? focused = FocusManager.Instance?.Current; + Visual? focused = FocusManager.Instance?.Current as Visual; while (focused != null) { @@ -784,11 +784,11 @@ namespace Avalonia.Controls // This helps deal with popups that may not be in the same // visual tree - IVisual? parent = focused.GetVisualParent(); + Visual? parent = focused.GetVisualParent(); if (parent == null) { // Try the logical parent. - IControl? element = focused as IControl; + Control? element = focused as Control; if (element != null) { parent = element.Parent; diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs index a192ef6420..35f94722ce 100644 --- a/src/Avalonia.Controls/Automation/AutomationProperties.cs +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -138,8 +138,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.LabeledBy attached property. /// - public static readonly AttachedProperty LabeledByProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty LabeledByProperty = + AvaloniaProperty.RegisterAttached( "LabeledBy", typeof(AutomationProperties)); @@ -503,7 +503,7 @@ namespace Avalonia.Automation /// /// Helper for setting LabeledBy property on a StyledElement. /// - public static void SetLabeledBy(StyledElement element, IControl value) + public static void SetLabeledBy(StyledElement element, Control value) { if (element == null) { @@ -516,7 +516,7 @@ namespace Avalonia.Automation /// /// Helper for reading LabeledBy property from a StyledElement. /// - public static IControl GetLabeledBy(StyledElement element) + public static Control GetLabeledBy(StyledElement element) { if (element == null) { diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs index 5ff291d972..5d71e7a8e4 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -71,7 +71,7 @@ namespace Avalonia.Automation.Peers } } - private ExpandCollapseState ToState(bool value) + private static ExpandCollapseState ToState(bool value) { return value ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; } diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index 25172b2d1c..e8fb6b75ad 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -58,7 +58,7 @@ namespace Avalonia.Automation.Peers protected virtual IReadOnlyList? GetChildrenCore() { - var children = ((IVisual)Owner).VisualChildren; + var children = Owner.VisualChildren; if (children.Count == 0) return null; @@ -176,10 +176,10 @@ namespace Avalonia.Automation.Peers { var root = control.GetVisualRoot(); - if (root is null) + if (root is not Visual rootVisual) return default; - var transform = control.TransformToVisual(root); + var transform = control.TransformToVisual(rootVisual); if (!transform.HasValue) return default; @@ -190,7 +190,7 @@ namespace Avalonia.Automation.Peers private void Initialize() { Owner.PropertyChanged += OwnerPropertyChanged; - var visualChildren = ((IVisual)Owner).VisualChildren; + var visualChildren = Owner.VisualChildren; visualChildren.CollectionChanged += VisualChildrenChanged; } diff --git a/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs index 4626e30ff1..7ae0ba7244 100644 --- a/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs @@ -37,7 +37,7 @@ namespace Avalonia.Automation.Peers { var container = owner.ItemContainerGenerator.ContainerFromIndex(i); - if (container is Control c && ((IVisual)c).IsAttachedToVisualTree) + if (container is Control c && c.IsAttachedToVisualTree) { var peer = GetOrCreate(c); diff --git a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs index 30b56bbd96..9ec65592fa 100644 --- a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs @@ -53,8 +53,9 @@ namespace Avalonia.Automation.Peers private void OnFocusChanged(IInputElement? focus) { var oldFocus = _focus; + var c = focus as Control; - _focus = focus?.VisualRoot == Owner ? focus as Control : null; + _focus = c?.VisualRoot == Owner ? c : null; if (_focus != oldFocus) { diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index c4587a2d9c..2710ac5cc2 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs index a92feec509..fe8b616e02 100644 --- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs @@ -206,7 +206,7 @@ namespace Avalonia.Controls.Primitives return true; } - private void EnsureValidThread() + private static void EnsureValidThread() { Dispatcher.UIThread.VerifyAccess(); } diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index eec3bdc9f2..032f452111 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -41,7 +41,7 @@ namespace Avalonia.Controls.Primitives private Button? _headerButton; private Button? _nextButton; private Button? _previousButton; - private ITemplate? _dayTitleTemplate; + private ITemplate? _dayTitleTemplate; private DateTime _currentMonth; private bool _isMouseLeftButtonDown = false; @@ -62,13 +62,13 @@ namespace Avalonia.Controls.Primitives get { return GetValue(HeaderBackgroundProperty); } set { SetValue(HeaderBackgroundProperty, value); } } - public static readonly DirectProperty?> DayTitleTemplateProperty = - AvaloniaProperty.RegisterDirect?>( + public static readonly DirectProperty?> DayTitleTemplateProperty = + AvaloniaProperty.RegisterDirect?>( nameof(DayTitleTemplate), o => o.DayTitleTemplate, (o,v) => o.DayTitleTemplate = v, defaultBindingMode: BindingMode.OneTime); - public ITemplate? DayTitleTemplate + public ITemplate? DayTitleTemplate { get { return _dayTitleTemplate; } set { SetAndRaise(DayTitleTemplateProperty, ref _dayTitleTemplate, value); } @@ -172,7 +172,7 @@ namespace Avalonia.Controls.Primitives if (MonthView != null) { var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth; - using var children = new PooledList(childCount); + using var children = new PooledList(childCount); for (int i = 0; i < Calendar.RowsPerMonth; i++) { @@ -217,7 +217,7 @@ namespace Avalonia.Controls.Primitives if (YearView != null) { var childCount = Calendar.RowsPerYear * Calendar.ColumnsPerYear; - using var children = new PooledList(childCount); + using var children = new PooledList(childCount); EventHandler monthCalendarButtonMouseDown = Month_CalendarButtonMouseDown; EventHandler monthCalendarButtonMouseUp = Month_CalendarButtonMouseUp; diff --git a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs index 211b5edb0d..ac4159d536 100644 --- a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs @@ -6,7 +6,6 @@ using Avalonia.Threading; using System; using System.Collections.ObjectModel; -using System.Threading; namespace Avalonia.Controls.Primitives { @@ -353,7 +352,7 @@ namespace Avalonia.Controls.Primitives return true; } - private void EnsureValidThread() + private static void EnsureValidThread() { Dispatcher.UIThread.VerifyAccess(); } diff --git a/src/Avalonia.Controls/Carousel.cs b/src/Avalonia.Controls/Carousel.cs index 28a4aa6436..572ec5a3d5 100644 --- a/src/Avalonia.Controls/Carousel.cs +++ b/src/Avalonia.Controls/Carousel.cs @@ -27,8 +27,8 @@ namespace Avalonia.Controls /// The default value of for /// . /// - private static readonly ITemplate PanelTemplate = - new FuncTemplate(() => new Panel()); + private static readonly ITemplate PanelTemplate = + new FuncTemplate(() => new Panel()); /// /// Initializes static members of the class. diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 54196bdf1a..2b407cc42a 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -29,8 +29,8 @@ namespace Avalonia.Controls /// /// The default value for the property. /// - private static readonly FuncTemplate DefaultPanel = - new FuncTemplate(() => new VirtualizingStackPanel()); + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new VirtualizingStackPanel()); /// /// Defines the property. @@ -275,7 +275,7 @@ namespace Avalonia.Controls protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); - if(!e.Handled && e.Source is IVisual source) + if(!e.Handled && e.Source is Visual source) { if (_popup?.IsInsidePopup(source) == true) { @@ -288,7 +288,7 @@ namespace Avalonia.Controls /// protected override void OnPointerReleased(PointerReleasedEventArgs e) { - if (!e.Handled && e.Source is IVisual source) + if (!e.Handled && e.Source is Visual source) { if (_popup?.IsInsidePopup(source) == true) { @@ -359,7 +359,7 @@ namespace Avalonia.Controls toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) => { //eat wheel scroll event outside dropdown popup while it's open - if (IsDropDownOpen && (ev.Source as IVisual)?.GetVisualRoot() == toplevel) + if (IsDropDownOpen && (ev.Source as Visual)?.GetVisualRoot() == toplevel) { ev.Handled = true; } @@ -368,7 +368,7 @@ namespace Avalonia.Controls this.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen); - foreach (var parent in this.GetVisualAncestors().OfType()) + foreach (var parent in this.GetVisualAncestors().OfType()) { parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen); } @@ -410,7 +410,7 @@ namespace Avalonia.Controls } } - private bool CanFocus(IControl control) => control.Focusable && control.IsEffectivelyEnabled && control.IsVisible; + private bool CanFocus(Control control) => control.Focusable && control.IsEffectivelyEnabled && control.IsVisible; private void UpdateSelectionBoxItem(object? item) { @@ -421,7 +421,7 @@ namespace Avalonia.Controls item = contentControl.Content; } - var control = item as IControl; + var control = item as Control; if (control != null) { @@ -456,7 +456,7 @@ namespace Avalonia.Controls { if ((rectangle.Fill as VisualBrush)?.Visual is Control content) { - var flowDirection = (((IVisual)content!).VisualParent as Control)?.FlowDirection ?? + var flowDirection = (((Visual)content!).VisualParent as Control)?.FlowDirection ?? FlowDirection.LeftToRight; rectangle.FlowDirection = flowDirection; } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 7b35e35278..020a6de539 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -77,8 +77,8 @@ namespace Avalonia.Controls public static readonly StyledProperty PlacementTargetProperty = Popup.PlacementTargetProperty.AddOwner(); - private static readonly ITemplate DefaultPanel = - new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); + private static readonly ITemplate DefaultPanel = + new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); private Popup? _popup; private List? _attachedControls; private IInputElement? _previousFocus; diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index beaee34b07..063e6ae7c8 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -26,13 +26,13 @@ namespace Avalonia.Controls /// - A property to allow user-defined data to be attached to the control. /// - and other context menu related members. /// - public class Control : InputElement, IControl, INamed, IVisualBrushInitialize, ISetterValue + public class Control : InputElement, IDataTemplateHost, INamed, IVisualBrushInitialize, ISetterValue { /// /// Defines the property. /// - public static readonly StyledProperty?> FocusAdornerProperty = - AvaloniaProperty.Register?>(nameof(FocusAdorner)); + public static readonly StyledProperty?> FocusAdornerProperty = + AvaloniaProperty.Register?>(nameof(FocusAdorner)); /// /// Defines the property. @@ -114,13 +114,13 @@ namespace Avalonia.Controls private bool _isLoaded = false; private DataTemplates? _dataTemplates; - private IControl? _focusAdorner; + private Control? _focusAdorner; private AutomationPeer? _automationPeer; /// /// Gets or sets the control's focus adorner. /// - public ITemplate? FocusAdorner + public ITemplate? FocusAdorner { get => GetValue(FocusAdornerProperty); set => SetValue(FocusAdornerProperty, value); @@ -227,7 +227,7 @@ namespace Avalonia.Controls remove => RemoveHandler(SizeChangedEvent, value); } - public new IControl? Parent => (IControl?)base.Parent; + public new Control? Parent => (Control?)base.Parent; /// /// Gets the value of the attached on a control. @@ -283,7 +283,7 @@ namespace Avalonia.Controls { foreach (var i in this.GetSelfAndVisualDescendants()) { - var c = i as IControl; + var c = i as Control; if (c?.IsInitialized == false && c is ISupportInitialize init) { @@ -305,7 +305,7 @@ namespace Avalonia.Controls /// Gets the element that receives the focus adorner. /// /// The control that receives the focus adorner. - protected virtual IControl? GetTemplateFocusTarget() => this; + protected virtual Control? GetTemplateFocusTarget() => this; private static Action loadedProcessingAction = () => { @@ -472,7 +472,7 @@ namespace Avalonia.Controls if (_focusAdorner?.Parent != null) { - var adornerLayer = (IPanel)_focusAdorner.Parent; + var adornerLayer = (Panel)_focusAdorner.Parent; adornerLayer.Children.Remove(_focusAdorner); _focusAdorner = null; } @@ -586,7 +586,7 @@ namespace Avalonia.Controls } /// - /// Computes the value according to the + /// Computes the value according to the /// and /// public virtual void InvalidateMirrorTransform() @@ -597,7 +597,7 @@ namespace Avalonia.Controls bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies; bool parentBypassFlowDirectionPolicies = false; - var parent = ((IVisual)this).VisualParent as Control; + var parent = this.VisualParent as Control; if (parent != null) { parentFlowDirection = parent.FlowDirection; diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index 1718679294..bceeb58adb 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -7,7 +7,7 @@ using Avalonia.Styling; namespace Avalonia.Controls { /// - /// Adds common functionality to . + /// Adds common functionality to . /// public static class ControlExtensions { @@ -15,7 +15,7 @@ namespace Avalonia.Controls /// Tries to bring the control into view. /// /// The control. - public static void BringIntoView(this IControl control) + public static void BringIntoView(this Control control) { _ = control ?? throw new ArgumentNullException(nameof(control)); @@ -27,7 +27,7 @@ namespace Avalonia.Controls /// /// The control. /// The area of the control to being into view. - public static void BringIntoView(this IControl control, Rect rect) + public static void BringIntoView(this Control control, Rect rect) { _ = control ?? throw new ArgumentNullException(nameof(control)); @@ -51,7 +51,7 @@ namespace Avalonia.Controls /// The control to look in. /// The name of the control to find. /// The control or null if not found. - public static T? FindControl(this IControl control, string name) where T : class, IControl + public static T? FindControl(this Control control, string name) where T : Control { _ = control ?? throw new ArgumentNullException(nameof(control)); _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -73,7 +73,7 @@ namespace Avalonia.Controls /// The control to look in. /// The name of the control to find. /// The control. - public static T GetControl(this IControl control, string name) where T : class, IControl + public static T GetControl(this Control control, string name) where T : Control { _ = control ?? throw new ArgumentNullException(nameof(control)); _ = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/Avalonia.Controls/Controls.cs b/src/Avalonia.Controls/Controls.cs index a504b39196..8b0e998f64 100644 --- a/src/Avalonia.Controls/Controls.cs +++ b/src/Avalonia.Controls/Controls.cs @@ -6,7 +6,7 @@ namespace Avalonia.Controls /// /// A collection of s. /// - public class Controls : AvaloniaList + public class Controls : AvaloniaList { /// /// Initializes a new instance of the class. @@ -20,7 +20,7 @@ namespace Avalonia.Controls /// Initializes a new instance of the class. /// /// The initial items in the collection. - public Controls(IEnumerable items) + public Controls(IEnumerable items) : base(items) { ResetBehavior = ResetBehavior.Remove; diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index 3fdfbee54d..9774b603e8 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -259,7 +259,7 @@ namespace Avalonia.Controls.Primitives SelectedValue = (int)newSel * Increment + MinimumValue; _suppressUpdateOffset = false; - System.Diagnostics.Debug.WriteLine($"Offset: {_offset} ItemHeight: {ItemHeight}"); + System.Diagnostics.Debug.WriteLine(FormattableString.Invariant($"Offset: {_offset} ItemHeight: {ItemHeight}")); } } @@ -545,7 +545,7 @@ namespace Avalonia.Controls.Primitives private void OnItemTapped(object? sender, TappedEventArgs e) { - if (e.Source is IVisual source && + if (e.Source is Visual source && GetItemFromSource(source) is ListBoxItem listBoxItem && listBoxItem.Tag is int tag) { @@ -555,7 +555,7 @@ namespace Avalonia.Controls.Primitives } //Helper to get ListBoxItem from pointerevent source - private ListBoxItem? GetItemFromSource(IVisual src) + private ListBoxItem? GetItemFromSource(Visual src) { var item = src; while (item != null && !(item is ListBoxItem)) @@ -565,9 +565,9 @@ namespace Avalonia.Controls.Primitives return (ListBoxItem?)item; } - public bool BringIntoView(IControl target, Rect targetRect) { return false; } + public bool BringIntoView(Control target, Rect targetRect) { return false; } - public IControl? GetControlInDirection(NavigationDirection direction, IControl? from) { return null; } + public Control? GetControlInDirection(NavigationDirection direction, Control? from) { return null; } public void RaiseScrollInvalidated(EventArgs e) { diff --git a/src/Avalonia.Controls/Decorator.cs b/src/Avalonia.Controls/Decorator.cs index bd866ee8d8..043fe48cc9 100644 --- a/src/Avalonia.Controls/Decorator.cs +++ b/src/Avalonia.Controls/Decorator.cs @@ -11,8 +11,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty ChildProperty = - AvaloniaProperty.Register(nameof(Child)); + public static readonly StyledProperty ChildProperty = + AvaloniaProperty.Register(nameof(Child)); /// /// Defines the property. @@ -33,7 +33,7 @@ namespace Avalonia.Controls /// Gets or sets the decorated control. /// [Content] - public IControl? Child + public Control? Child { get { return GetValue(ChildProperty); } set { SetValue(ChildProperty, value); } diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 5b68402f87..12a096b105 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -125,7 +125,7 @@ namespace Avalonia.Controls.Documents /// Adds a control wrapped inside a to the collection. /// /// The to be added control. - public void Add(IControl control) + public void Add(Control control) { Add(new InlineUIContainer(control)); } diff --git a/src/Avalonia.Controls/Documents/InlineRun.cs b/src/Avalonia.Controls/Documents/InlineRun.cs index 68c61ca3fa..1090b1334a 100644 --- a/src/Avalonia.Controls/Documents/InlineRun.cs +++ b/src/Avalonia.Controls/Documents/InlineRun.cs @@ -6,13 +6,13 @@ namespace Avalonia.Controls.Documents { internal class EmbeddedControlRun : DrawableTextRun { - public EmbeddedControlRun(IControl control, TextRunProperties properties) + public EmbeddedControlRun(Control control, TextRunProperties properties) { Control = control; Properties = properties; } - public IControl Control { get; } + public Control Control { get; } public override TextRunProperties? Properties { get; } diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs index 7107fb7fed..58afb24b5c 100644 --- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs +++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs @@ -15,8 +15,8 @@ namespace Avalonia.Controls.Documents /// /// Defines the property. /// - public static readonly StyledProperty ChildProperty = - AvaloniaProperty.Register(nameof(Child)); + public static readonly StyledProperty ChildProperty = + AvaloniaProperty.Register(nameof(Child)); static InlineUIContainer() { @@ -41,7 +41,7 @@ namespace Avalonia.Controls.Documents /// /// UIElement set as a child of this inline item /// - public InlineUIContainer(IControl child) + public InlineUIContainer(Control child) { Child = child; } @@ -50,7 +50,7 @@ namespace Avalonia.Controls.Documents /// The content spanned by this TextElement. /// [Content] - public IControl Child + public Control Child { get => GetValue(ChildProperty); set => SetValue(ChildProperty, value); diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 1c7bdb9b37..4f80892857 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls.Embedding.Offscreen IsDisposed = true; } - public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root); public abstract void Invalidate(Rect rect); public abstract IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index 3ba99d8a67..65227a826a 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -1,7 +1,11 @@ +using System; using System.Threading; using Avalonia.Animation; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Interactivity; +using Avalonia.Threading; namespace Avalonia.Controls { @@ -37,12 +41,24 @@ namespace Avalonia.Controls [PseudoClasses(":expanded", ":up", ":down", ":left", ":right")] public class Expander : HeaderedContentControl { + /// + /// Defines the property. + /// public static readonly StyledProperty ContentTransitionProperty = - AvaloniaProperty.Register(nameof(ContentTransition)); + AvaloniaProperty.Register( + nameof(ContentTransition)); + /// + /// Defines the property. + /// public static readonly StyledProperty ExpandDirectionProperty = - AvaloniaProperty.Register(nameof(ExpandDirection), ExpandDirection.Down); + AvaloniaProperty.Register( + nameof(ExpandDirection), + ExpandDirection.Down); + /// + /// Defines the property. + /// public static readonly DirectProperty IsExpandedProperty = AvaloniaProperty.RegisterDirect( nameof(IsExpanded), @@ -50,47 +66,206 @@ namespace Avalonia.Controls (o, v) => o.IsExpanded = v, defaultBindingMode: Data.BindingMode.TwoWay); + /// + /// Defines the event. + /// + public static readonly RoutedEvent CollapsedEvent = + RoutedEvent.Register( + nameof(Collapsed), + RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent CollapsingEvent = + RoutedEvent.Register( + nameof(Collapsing), + RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent ExpandedEvent = + RoutedEvent.Register( + nameof(Expanded), + RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent ExpandingEvent = + RoutedEvent.Register( + nameof(Expanding), + RoutingStrategies.Bubble); + + private bool _ignorePropertyChanged = false; private bool _isExpanded; private CancellationTokenSource? _lastTransitionCts; - static Expander() - { - IsExpandedProperty.Changed.AddClassHandler((x, e) => x.OnIsExpandedChanged(e)); - } - + /// + /// Initializes a new instance of the class. + /// public Expander() { - UpdatePseudoClasses(ExpandDirection); + UpdatePseudoClasses(); } + /// + /// Gets or sets the transition used when expanding or collapsing the content. + /// public IPageTransition? ContentTransition { get => GetValue(ContentTransitionProperty); set => SetValue(ContentTransitionProperty, value); } + /// + /// Gets or sets the direction in which the opens. + /// public ExpandDirection ExpandDirection { get => GetValue(ExpandDirectionProperty); set => SetValue(ExpandDirectionProperty, value); } + /// + /// Gets or sets a value indicating whether the + /// content area is open and visible. + /// public bool IsExpanded { - get { return _isExpanded; } - set - { - SetAndRaise(IsExpandedProperty, ref _isExpanded, value); - PseudoClasses.Set(":expanded", value); + get => _isExpanded; + set + { + // It is important here that IsExpanded is a direct property so events can be invoked + // BEFORE the property system gets notified of updated values. This is because events + // may be canceled by external code. + if (_isExpanded != value) + { + RoutedEventArgs eventArgs; + + if (value) + { + eventArgs = new RoutedEventArgs(ExpandingEvent, this); + OnExpanding(eventArgs); + } + else + { + eventArgs = new RoutedEventArgs(CollapsingEvent, this); + OnCollapsing(eventArgs); + } + + if (eventArgs.Handled) + { + // If the event was externally handled (canceled) we must still notify the value has changed. + // This property changed notification will update any external code observing this property that itself may have set the new value. + // We are essentially reverted any external state change along with ignoring the IsExpanded property set. + // Remember IsExpanded is usually controlled by a ToggleButton in the control theme. + _ignorePropertyChanged = true; + + RaisePropertyChanged( + IsExpandedProperty, + oldValue: value, + newValue: _isExpanded, + BindingPriority.LocalValue, + isEffectiveValue: true); + + _ignorePropertyChanged = false; + } + else + { + SetAndRaise(IsExpandedProperty, ref _isExpanded, value); + } + } } } - protected virtual async void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e) + /// + /// Occurs after the content area has closed and only the header is visible. + /// + public event EventHandler? Collapsed + { + add => AddHandler(CollapsedEvent, value); + remove => RemoveHandler(CollapsedEvent, value); + } + + /// + /// Occurs as the content area is closing. + /// + /// + /// The event args property may be set to true to cancel the event + /// and keep the control open (expanded). + /// + public event EventHandler? Collapsing + { + add => AddHandler(CollapsingEvent, value); + remove => RemoveHandler(CollapsingEvent, value); + } + + /// + /// Occurs after the has opened to display both its header and content. + /// + public event EventHandler? Expanded + { + add => AddHandler(ExpandedEvent, value); + remove => RemoveHandler(ExpandedEvent, value); + } + + /// + /// Occurs as the content area is opening. + /// + /// + /// The event args property may be set to true to cancel the event + /// and keep the control closed (collapsed). + /// + public event EventHandler? Expanding + { + add => AddHandler(ExpandingEvent, value); + remove => RemoveHandler(ExpandingEvent, value); + } + + /// + /// Invoked just before the event. + /// + protected virtual void OnCollapsed(RoutedEventArgs eventArgs) + { + RaiseEvent(eventArgs); + } + + /// + /// Invoked just before the event. + /// + protected virtual void OnCollapsing(RoutedEventArgs eventArgs) + { + RaiseEvent(eventArgs); + } + + /// + /// Invoked just before the event. + /// + protected virtual void OnExpanded(RoutedEventArgs eventArgs) + { + RaiseEvent(eventArgs); + } + + /// + /// Invoked just before the event. + /// + protected virtual void OnExpanding(RoutedEventArgs eventArgs) + { + RaiseEvent(eventArgs); + } + + /// + /// Starts the content transition (if set) and invokes the + /// and events when completed. + /// + private async void StartContentTransition() { if (Content != null && ContentTransition != null && Presenter is Visual visualContent) { bool forward = ExpandDirection == ExpandDirection.Left || - ExpandDirection == ExpandDirection.Up; + ExpandDirection == ExpandDirection.Up; _lastTransitionCts?.Cancel(); _lastTransitionCts = new CancellationTokenSource(); @@ -104,24 +279,58 @@ namespace Avalonia.Controls await ContentTransition.Start(visualContent, null, forward, _lastTransitionCts.Token); } } + + // Expanded/Collapsed events are invoked asynchronously to ensure other events, + // such as Click, have time to complete first. + Dispatcher.UIThread.Post(() => + { + if (IsExpanded) + { + OnExpanded(new RoutedEventArgs(ExpandedEvent, this)); + } + else + { + OnCollapsed(new RoutedEventArgs(CollapsedEvent, this)); + } + }); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); + if (_ignorePropertyChanged) + { + return; + } + if (change.Property == ExpandDirectionProperty) { - UpdatePseudoClasses(change.GetNewValue()); + UpdatePseudoClasses(); + } + else if (change.Property == IsExpandedProperty) + { + // Expanded/Collapsed will be raised once transitions are complete + StartContentTransition(); + + UpdatePseudoClasses(); } } - private void UpdatePseudoClasses(ExpandDirection d) + /// + /// Updates the visual state of the control by applying latest PseudoClasses. + /// + private void UpdatePseudoClasses() { - PseudoClasses.Set(":up", d == ExpandDirection.Up); - PseudoClasses.Set(":down", d == ExpandDirection.Down); - PseudoClasses.Set(":left", d == ExpandDirection.Left); - PseudoClasses.Set(":right", d == ExpandDirection.Right); + var expandDirection = ExpandDirection; + + PseudoClasses.Set(":up", expandDirection == ExpandDirection.Up); + PseudoClasses.Set(":down", expandDirection == ExpandDirection.Down); + PseudoClasses.Set(":left", expandDirection == ExpandDirection.Left); + PseudoClasses.Set(":right", expandDirection == ExpandDirection.Right); + + PseudoClasses.Set(":expanded", IsExpanded); } } } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 65ec4cc54c..3ff248f0be 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -325,14 +325,14 @@ namespace Avalonia.Controls.Primitives return; } - if (Popup?.Host is PopupRoot) + if (Popup?.Host is PopupRoot && pArgs.Root is Visual eventRoot) { // As long as the pointer stays within the enlargedPopupRect // the flyout stays open. If it leaves, close it // Despite working in screen coordinates, leaving the TopLevel // window will not close this (as pointer events stop), which // does match UWP - var pt = pArgs.Root.PointToScreen(pArgs.Position); + var pt = eventRoot.PointToScreen(pArgs.Position); if (!_enlargePopupRectScreenPixelRect?.Contains(pt) ?? false) { HideCore(false); @@ -587,7 +587,7 @@ namespace Avalonia.Controls.Primitives return eventArgs.Cancel; } - internal static void SetPresenterClasses(IControl? presenter, Classes classes) + internal static void SetPresenterClasses(Control? presenter, Classes classes) { if(presenter is null) { diff --git a/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs index 0f257224dd..8590974462 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls } } - base.OnKeyDown(e); + base.OnKeyDown(e); } } } diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs index 79c77f2519..8f0e3c88c1 100644 --- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs @@ -102,13 +102,13 @@ namespace Avalonia.Controls.Generators /// /// The index. /// The container, or null if no container created. - IControl? ContainerFromIndex(int index); + Control? ContainerFromIndex(int index); /// /// Gets the index of the specified container control. /// /// The container. /// The index of the container, or -1 if not found. - int IndexFromContainer(IControl? container); + int IndexFromContainer(Control? container); } } diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index 4ce48d2a06..d87422e57e 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls.Generators /// Initializes a new instance of the class. /// /// The owner control. - public ItemContainerGenerator(IControl owner) + public ItemContainerGenerator(Control owner) { Owner = owner ?? throw new ArgumentNullException(nameof(owner)); } @@ -52,7 +52,7 @@ namespace Avalonia.Controls.Generators /// /// Gets the owner control. /// - public IControl Owner { get; } + public Control Owner { get; } /// public virtual Type? ContainerType => null; @@ -159,7 +159,7 @@ namespace Avalonia.Controls.Generators } /// - public IControl? ContainerFromIndex(int index) + public Control? ContainerFromIndex(int index) { ItemContainerInfo? result; _containers.TryGetValue(index, out result); @@ -167,7 +167,7 @@ namespace Avalonia.Controls.Generators } /// - public int IndexFromContainer(IControl? container) + public int IndexFromContainer(Control? container) { foreach (var i in _containers) { @@ -185,9 +185,9 @@ namespace Avalonia.Controls.Generators /// /// The item. /// The created container control. - protected virtual IControl? CreateContainer(object item) + protected virtual Control? CreateContainer(object item) { - var result = item as IControl; + var result = item as Control; if (result == null) { diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs index 5e965a9d04..ad5bf0219c 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Controls.Templates; using Avalonia.Data; namespace Avalonia.Controls.Generators @@ -8,7 +7,7 @@ namespace Avalonia.Controls.Generators /// Creates containers for items and maintains a list of created containers. /// /// The type of the container. - public class ItemContainerGenerator : ItemContainerGenerator where T : class, IControl, new() + public class ItemContainerGenerator : ItemContainerGenerator where T : Control, new() { /// /// Initializes a new instance of the class. @@ -17,7 +16,7 @@ namespace Avalonia.Controls.Generators /// The container's Content property. /// The container's ContentTemplate property. public ItemContainerGenerator( - IControl owner, + Control owner, AvaloniaProperty contentProperty, AvaloniaProperty? contentTemplateProperty) : base(owner) @@ -40,7 +39,7 @@ namespace Avalonia.Controls.Generators protected AvaloniaProperty? ContentTemplateProperty { get; } /// - protected override IControl? CreateContainer(object item) + protected override Control? CreateContainer(object item) { var container = item as T; @@ -63,7 +62,7 @@ namespace Avalonia.Controls.Generators container.SetValue(ContentProperty, item, BindingPriority.Style); } - if (!(item is IControl)) + if (!(item is Control)) { container.DataContext = item; } @@ -89,7 +88,7 @@ namespace Avalonia.Controls.Generators container.SetValue(ContentProperty, item); - if (!(item is IControl)) + if (!(item is Control)) { container.DataContext = item; } diff --git a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs index 31d9a5c02e..047e457e7c 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Generators /// /// The index of the item in the collection. /// - public ItemContainerInfo(IControl container, object item, int index) + public ItemContainerInfo(Control container, object item, int index) { ContainerControl = container; Item = item; @@ -27,7 +27,7 @@ namespace Avalonia.Controls.Generators /// /// This will be null if is null. /// - public IControl ContainerControl { get; } + public Control ContainerControl { get; } /// /// Gets the item that the container represents. diff --git a/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs index bfcb474515..0dcb6c05e1 100644 --- a/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs @@ -6,13 +6,13 @@ /// Initializes a new instance of the class. /// /// The owner control. - public MenuItemContainerGenerator(IControl owner) + public MenuItemContainerGenerator(Control owner) : base(owner, MenuItem.HeaderProperty, null) { } /// - protected override IControl? CreateContainer(object item) + protected override Control? CreateContainer(object item) { var separator = item as Separator; return separator != null ? separator : base.CreateContainer(item); diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs index 4021b8436a..b26b113fbb 100644 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls.Generators public new TabControl Owner { get; } - protected override IControl CreateContainer(object item) + protected override Control CreateContainer(object item) { var tabItem = (TabItem)base.CreateContainer(item)!; @@ -48,14 +48,14 @@ namespace Avalonia.Controls.Generators } else { - if (!(tabItem.DataContext is IControl)) + if (!(tabItem.DataContext is Control)) { tabItem.Header = tabItem.DataContext; } } } - if (!(tabItem.Content is IControl)) + if (!(tabItem.Content is Control)) { tabItem.Bind(TabItem.ContentTemplateProperty, new OwnerBinding( tabItem, diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs index eb60fca367..02b8e7003e 100644 --- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs +++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs @@ -15,9 +15,9 @@ namespace Avalonia.Controls.Generators /// public class TreeContainerIndex { - private readonly Dictionary> _itemToContainerSet = new Dictionary>(); - private readonly Dictionary _itemToContainer = new Dictionary(); - private readonly Dictionary _containerToItem = new Dictionary(); + private readonly Dictionary> _itemToContainerSet = new Dictionary>(); + private readonly Dictionary _itemToContainer = new Dictionary(); + private readonly Dictionary _containerToItem = new Dictionary(); /// /// Signaled whenever new containers are materialized. @@ -32,7 +32,7 @@ namespace Avalonia.Controls.Generators /// /// Gets the currently materialized containers. /// - public IEnumerable Containers => _containerToItem.Keys; + public IEnumerable Containers => _containerToItem.Keys; /// /// Gets the items of currently materialized containers. @@ -44,7 +44,7 @@ namespace Avalonia.Controls.Generators /// /// The item. /// The item container. - public void Add(object item, IControl container) + public void Add(object item, Control container) { _itemToContainer[item] = container; if (_itemToContainerSet.TryGetValue(item, out var set)) @@ -53,7 +53,7 @@ namespace Avalonia.Controls.Generators } else { - _itemToContainerSet.Add(item, new HashSet { container }); + _itemToContainerSet.Add(item, new HashSet { container }); } _containerToItem.Add(container, item); @@ -68,7 +68,7 @@ namespace Avalonia.Controls.Generators /// /// The item container. /// The DataContext object - private void RemoveContainer(IControl container, object item) + private void RemoveContainer(Control container, object item) { if (_itemToContainerSet.TryGetValue(item, out var set)) { @@ -89,7 +89,7 @@ namespace Avalonia.Controls.Generators /// Removes a container from the index. /// /// The item container. - public void Remove(IControl container) + public void Remove(Control container) { var item = _containerToItem[container]; _containerToItem.Remove(container); @@ -124,7 +124,7 @@ namespace Avalonia.Controls.Generators /// /// The item. /// The container, or null of not found. - public IControl? ContainerFromItem(object item) + public Control? ContainerFromItem(object item) { if (item != null) { @@ -148,7 +148,7 @@ namespace Avalonia.Controls.Generators /// /// The container. /// The item, or null of not found. - public object? ItemFromContainer(IControl? container) + public object? ItemFromContainer(Control? container) { if (container != null) { diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 2d8cb05e03..b7111c0ef2 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Generators /// /// The type of the container. public class TreeItemContainerGenerator : ItemContainerGenerator, ITreeItemContainerGenerator - where T : class, IControl, new() + where T : Control, new() { private TreeView? _treeView; @@ -25,7 +25,7 @@ namespace Avalonia.Controls.Generators /// The container's Items property. /// The container's IsExpanded property. public TreeItemContainerGenerator( - IControl owner, + Control owner, AvaloniaProperty contentProperty, AvaloniaProperty contentTemplateProperty, AvaloniaProperty itemsProperty, @@ -53,7 +53,7 @@ namespace Avalonia.Controls.Generators protected AvaloniaProperty IsExpandedProperty { get; } /// - protected override IControl? CreateContainer(object? item) + protected override Control? CreateContainer(object? item) { var container = item as T; @@ -93,7 +93,7 @@ namespace Avalonia.Controls.Generators BindingOperations.Apply(result, ItemsProperty, itemsSelector, null); } - if (!(item is IControl)) + if (!(item is Control)) { result.DataContext = item; } @@ -149,7 +149,7 @@ namespace Avalonia.Controls.Generators { private readonly IDataTemplate _inner; public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner; - public IControl? Build(object? param) => _inner.Build(param); + public Control? Build(object? param) => _inner.Build(param); public bool Match(object? data) => _inner.Match(data); public InstancedBinding? ItemsSelector(object item) => null; } diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 8d246d35f4..7737fdac2e 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1165,7 +1165,7 @@ namespace Avalonia.Controls /// /// For "Auto" definitions MinWidth is used in place of PreferredSize. /// - private double GetMeasureSizeForRange( + private static double GetMeasureSizeForRange( IReadOnlyList definitions, int start, int count) @@ -1192,7 +1192,7 @@ namespace Avalonia.Controls /// Starting index of the range. /// Number of definitions included in the range. /// Length type for given range. - private LayoutTimeSizeType GetLengthTypeForRange( + private static LayoutTimeSizeType GetLengthTypeForRange( IReadOnlyList definitions, int start, int count) @@ -1721,7 +1721,7 @@ namespace Avalonia.Controls /// /// Array of definitions to use for calculations. /// Desired size. - private double CalculateDesiredSize( + private static double CalculateDesiredSize( IReadOnlyList definitions) { double desiredSize = 0; @@ -2281,7 +2281,7 @@ namespace Avalonia.Controls /// Start of the range. /// Number of items in the range. /// Final size. - private double GetFinalSizeForRange( + private static double GetFinalSizeForRange( IReadOnlyList definitions, int start, int count) diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index 85dad894fd..4684304725 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -53,8 +53,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty> PreviewContentProperty = - AvaloniaProperty.Register>(nameof(PreviewContent)); + public static readonly StyledProperty> PreviewContentProperty = + AvaloniaProperty.Register>(nameof(PreviewContent)); private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast); private static readonly Cursor s_rowSplitterCursor = new Cursor(StandardCursorType.SizeNorthSouth); @@ -109,7 +109,7 @@ namespace Avalonia.Controls /// /// Gets or sets content that will be shown when is enabled and user starts resize operation. /// - public ITemplate PreviewContent + public ITemplate PreviewContent { get => GetValue(PreviewContentProperty); set => SetValue(PreviewContentProperty, value); @@ -336,7 +336,7 @@ namespace Avalonia.Controls return; } - IControl? builtPreviewContent = previewContent?.Build(); + Control? builtPreviewContent = previewContent?.Build(); _resizeData.Adorner = new PreviewAdorner(builtPreviewContent); @@ -516,7 +516,7 @@ namespace Avalonia.Controls /// /// Retrieves the ActualWidth or ActualHeight of the definition depending on its type Column or Row. /// - private double GetActualLength(DefinitionBase definition) + private static double GetActualLength(DefinitionBase definition) { var column = definition as ColumnDefinition; @@ -696,7 +696,7 @@ namespace Avalonia.Controls private readonly TranslateTransform _translation; private readonly Decorator _decorator; - public PreviewAdorner(IControl? previewControl) + public PreviewAdorner(Control? previewControl) { // Add a decorator to perform translations. _translation = new TranslateTransform(); diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index bde1da509b..dc3c621db4 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls { readonly WeakReference reference; - public HotkeyCommandWrapper(IControl control) + public HotkeyCommandWrapper(Control control) { reference = new WeakReference(control); } @@ -76,7 +76,7 @@ namespace Avalonia.Controls class Manager { - private readonly IControl _control; + private readonly Control _control; private TopLevel? _root; private IDisposable? _parentSub; private IDisposable? _hotkeySub; @@ -84,7 +84,7 @@ namespace Avalonia.Controls private readonly HotkeyCommandWrapper _wrapper; private KeyBinding? _binding; - public Manager(IControl control) + public Manager(Control control) { _control = control; _wrapper = new HotkeyCommandWrapper(_control); @@ -147,7 +147,7 @@ namespace Avalonia.Controls if (args.NewValue.Value is null) return; - var control = args.Sender as IControl; + var control = args.Sender as Control; if (control is not IClickableControl) { Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?.Log(control, diff --git a/src/Avalonia.Controls/IContentControl.cs b/src/Avalonia.Controls/IContentControl.cs index b4d8d0f574..b2828c26e8 100644 --- a/src/Avalonia.Controls/IContentControl.cs +++ b/src/Avalonia.Controls/IContentControl.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls /// . /// [NotClientImplementable] - public interface IContentControl : IControl + public interface IContentControl { /// /// Gets or sets the content to display. diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs deleted file mode 100644 index 3395fc1059..0000000000 --- a/src/Avalonia.Controls/IControl.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Avalonia.Controls.Templates; -using Avalonia.Input; -using Avalonia.Layout; -using Avalonia.Metadata; -using Avalonia.VisualTree; - -namespace Avalonia.Controls -{ - /// - /// Interface for Avalonia controls. - /// - [NotClientImplementable] - public interface IControl : IVisual, - IDataTemplateHost, - ILayoutable, - IInputElement, - INamed, - IStyledElement - { - new IControl? Parent { get; } - } -} diff --git a/src/Avalonia.Controls/IMenu.cs b/src/Avalonia.Controls/IMenu.cs index d90c5ea7a8..ddf2997014 100644 --- a/src/Avalonia.Controls/IMenu.cs +++ b/src/Avalonia.Controls/IMenu.cs @@ -1,5 +1,7 @@ using Avalonia.Controls.Platform; +using Avalonia.Input; using Avalonia.Metadata; +using Avalonia.Rendering; namespace Avalonia.Controls { @@ -7,7 +9,7 @@ namespace Avalonia.Controls /// Represents a or . /// [NotClientImplementable] - public interface IMenu : IMenuElement + public interface IMenu : IMenuElement, IInputElement { /// /// Gets the menu interaction handler. @@ -18,5 +20,10 @@ namespace Avalonia.Controls /// Gets a value indicating whether the menu is open. /// bool IsOpen { get; } + + /// + /// Gets the root of the visual tree, if the control is attached to a visual tree. + /// + IRenderRoot? VisualRoot { get; } } } diff --git a/src/Avalonia.Controls/IMenuElement.cs b/src/Avalonia.Controls/IMenuElement.cs index c13c20b639..2a81d20bd9 100644 --- a/src/Avalonia.Controls/IMenuElement.cs +++ b/src/Avalonia.Controls/IMenuElement.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Avalonia.Input; +using Avalonia.LogicalTree; using Avalonia.Metadata; namespace Avalonia.Controls @@ -8,7 +9,7 @@ namespace Avalonia.Controls /// Represents an or . /// [NotClientImplementable] - public interface IMenuElement : IControl + public interface IMenuElement : IInputElement, ILogical { /// /// Gets or sets the currently selected submenu item. diff --git a/src/Avalonia.Controls/IMenuItem.cs b/src/Avalonia.Controls/IMenuItem.cs index 9d7ef3c18d..1257d33684 100644 --- a/src/Avalonia.Controls/IMenuItem.cs +++ b/src/Avalonia.Controls/IMenuItem.cs @@ -38,7 +38,7 @@ namespace Avalonia.Controls /// /// Gets the parent . /// - new IMenuElement? Parent { get; } + IMenuElement? Parent { get; } /// /// Raises a click event on the menu item. diff --git a/src/Avalonia.Controls/IPanel.cs b/src/Avalonia.Controls/IPanel.cs deleted file mode 100644 index 8f2564ec74..0000000000 --- a/src/Avalonia.Controls/IPanel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Avalonia.Metadata; - -namespace Avalonia.Controls -{ - /// - /// Interface for controls that can contain multiple children. - /// - [NotClientImplementable] - public interface IPanel : IControl - { - /// - /// Gets the children of the . - /// - Controls Children { get; } - } -} diff --git a/src/Avalonia.Controls/IScrollAnchorProvider.cs b/src/Avalonia.Controls/IScrollAnchorProvider.cs index b3ce7ef9e8..07663b057c 100644 --- a/src/Avalonia.Controls/IScrollAnchorProvider.cs +++ b/src/Avalonia.Controls/IScrollAnchorProvider.cs @@ -8,7 +8,7 @@ /// /// The currently chosen anchor element to use for scroll anchoring. /// - IControl? CurrentAnchor { get; } + Control? CurrentAnchor { get; } /// /// Registers a control as a potential scroll anchor candidate. @@ -16,7 +16,7 @@ /// /// A control within the subtree of the . /// - void RegisterAnchorCandidate(IControl element); + void RegisterAnchorCandidate(Control element); /// /// Unregisters a control as a potential scroll anchor candidate. @@ -24,6 +24,6 @@ /// /// A control within the subtree of the . /// - void UnregisterAnchorCandidate(IControl element); + void UnregisterAnchorCandidate(Control element); } } diff --git a/src/Avalonia.Controls/IVirtualizingPanel.cs b/src/Avalonia.Controls/IVirtualizingPanel.cs index 604a62dcb9..9cdbc69cfb 100644 --- a/src/Avalonia.Controls/IVirtualizingPanel.cs +++ b/src/Avalonia.Controls/IVirtualizingPanel.cs @@ -5,8 +5,13 @@ namespace Avalonia.Controls /// /// A panel that can be used to virtualize items. /// - public interface IVirtualizingPanel : IPanel + public interface IVirtualizingPanel { + /// + /// Gets the children of the panel. + /// + Controls Children { get; } + /// /// Gets or sets the controller for the virtualizing panel. /// @@ -74,8 +79,8 @@ namespace Avalonia.Controls /// /// /// The implementation for this method should call - /// and also ensure that the next call to - /// calls + /// and also ensure that the next call to + /// calls /// on the next measure even if /// the available size hasn't changed. /// diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index e9ce7912a7..e6c3d67bf4 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -29,8 +29,8 @@ namespace Avalonia.Controls /// /// The default value for the property. /// - private static readonly FuncTemplate DefaultPanel = - new FuncTemplate(() => new StackPanel()); + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new StackPanel()); /// /// Defines the property. @@ -53,8 +53,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty> ItemsPanelProperty = - AvaloniaProperty.Register>(nameof(ItemsPanel), DefaultPanel); + public static readonly StyledProperty> ItemsPanelProperty = + AvaloniaProperty.Register>(nameof(ItemsPanel), DefaultPanel); /// /// Defines the property. @@ -156,7 +156,7 @@ namespace Avalonia.Controls /// /// Gets or sets the panel used to display the items. /// - public ITemplate ItemsPanel + public ITemplate ItemsPanel { get { return GetValue(ItemsPanelProperty); } set { SetValue(ItemsPanelProperty, value); } @@ -348,7 +348,7 @@ namespace Avalonia.Controls return; } - IVisual? current = focus.Current; + Visual? current = focus.Current as Visual; while (current != null) { @@ -453,7 +453,7 @@ namespace Avalonia.Controls { foreach (var i in items) { - var control = i as IControl; + var control = i as Control; if (control != null && !LogicalChildren.Contains(control)) { @@ -477,7 +477,7 @@ namespace Avalonia.Controls { foreach (var i in items) { - var control = i as IControl; + var control = i as Control; if (control != null) { diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index e896c13f25..5668b79e81 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -52,7 +52,7 @@ namespace Avalonia.Controls set { SetValue(UseRenderTransformProperty, value); } } - public IControl? TransformRoot => Child; + public Control? TransformRoot => Child; /// /// Provides the behavior for the "Arrange" pass of layout. diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 86118d7b00..7e5981688c 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -20,8 +20,8 @@ namespace Avalonia.Controls /// /// The default value for the property. /// - private static readonly FuncTemplate DefaultPanel = - new FuncTemplate(() => new VirtualizingStackPanel()); + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new VirtualizingStackPanel()); /// /// Defines the property. @@ -149,7 +149,7 @@ namespace Avalonia.Controls { base.OnPointerPressed(e); - if (e.Source is IVisual source) + if (e.Source is Visual source) { var point = e.GetCurrentPoint(source); diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index 611811f170..c50cce9589 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -14,8 +14,8 @@ namespace Avalonia.Controls /// public class Menu : MenuBase, IMainMenu { - private static readonly ITemplate DefaultPanel = - new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); + private static readonly ITemplate DefaultPanel = + new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); /// diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs index 54cbc46a36..bd7a2749c5 100644 --- a/src/Avalonia.Controls/MenuBase.cs +++ b/src/Avalonia.Controls/MenuBase.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Rendering; namespace Avalonia.Controls { @@ -74,6 +75,8 @@ namespace Avalonia.Controls /// IMenuInteractionHandler IMenu.InteractionHandler => InteractionHandler; + IRenderRoot? IMenu.VisualRoot => VisualRoot; + /// IMenuItem? IMenuElement.SelectedItem { @@ -86,8 +89,8 @@ namespace Avalonia.Controls } set { - SelectedIndex = value is not null ? - ItemContainerGenerator.IndexFromContainer(value) : -1; + SelectedIndex = value is Control c ? + ItemContainerGenerator.IndexFromContainer(c) : -1; } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 11c42f2ef3..3e00cea430 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -110,8 +110,8 @@ namespace Avalonia.Controls /// /// The default value for the property. /// - private static readonly ITemplate DefaultPanel = - new FuncTemplate(() => new StackPanel()); + private static readonly ITemplate DefaultPanel = + new FuncTemplate(() => new StackPanel()); private ICommand? _command; private bool _commandCanExecute = true; @@ -323,7 +323,7 @@ namespace Avalonia.Controls } set { - SelectedIndex = value is not null ? ItemContainerGenerator.IndexFromContainer(value) : -1; + SelectedIndex = value is Control c ? ItemContainerGenerator.IndexFromContainer(c) : -1; } } @@ -450,7 +450,7 @@ namespace Avalonia.Controls base.OnPointerEntered(e); var point = e.GetCurrentPoint(null); - RaiseEvent(new PointerEventArgs(PointerEnteredItemEvent, this, e.Pointer, this.VisualRoot, point.Position, + RaiseEvent(new PointerEventArgs(PointerEnteredItemEvent, this, e.Pointer, (Visual?)VisualRoot, point.Position, e.Timestamp, point.Properties, e.KeyModifiers)); } @@ -460,7 +460,7 @@ namespace Avalonia.Controls base.OnPointerExited(e); var point = e.GetCurrentPoint(null); - RaiseEvent(new PointerEventArgs(PointerExitedItemEvent, this, e.Pointer, this.VisualRoot, point.Position, + RaiseEvent(new PointerEventArgs(PointerExitedItemEvent, this, e.Pointer, (Visual?)VisualRoot, point.Position, e.Timestamp, point.Properties, e.KeyModifiers)); } diff --git a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs index 4cc6a20082..381f1799d4 100644 --- a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs +++ b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// /// 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. @@ -59,12 +59,12 @@ namespace Avalonia.Controls { 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)); } /// @@ -88,9 +88,10 @@ namespace Avalonia.Controls { if (!string.IsNullOrWhiteSpace(e.Text)) { - var text = e.Text.ToUpper(); + var text = e.Text; var focus = _registered - .FirstOrDefault(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)?.Item2; + .FirstOrDefault(x => string.Equals(x.AccessKey, text, StringComparison.OrdinalIgnoreCase) + && x.Element.IsEffectivelyVisible).Element; focus?.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); diff --git a/src/Avalonia.Controls/Mixins/SelectableMixin.cs b/src/Avalonia.Controls/Mixins/SelectableMixin.cs index fa964c0056..a04a741e3e 100644 --- a/src/Avalonia.Controls/Mixins/SelectableMixin.cs +++ b/src/Avalonia.Controls/Mixins/SelectableMixin.cs @@ -38,7 +38,7 @@ namespace Avalonia.Controls.Mixins /// The control type. /// The IsSelected property. public static void Attach(AvaloniaProperty isSelected) - where TControl : class, IControl + where TControl : Control { _ = isSelected ?? throw new ArgumentNullException(nameof(isSelected)); diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 191b60f303..bcf0866129 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; - var visual = (IVisual)this; + var visual = (Visual)this; while (visual != null) { if (visual is Visual v) diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index ae473f8500..8b3e875e5a 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; @@ -21,7 +22,16 @@ namespace Avalonia.Controls item.Click -= OnMenuItemClick; }); } - + + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenu))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItem))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemBase))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemSeparator))] + public NativeMenuBar() + { + + } + public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable) { menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable); diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 45beaa0b2f..46c772f3b1 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -146,7 +146,11 @@ namespace Avalonia.Controls.Notifications { var adornerLayer = host.FindDescendantOfType()?.AdornerLayer; - adornerLayer?.Children.Add(this); + if (adornerLayer is not null) + { + adornerLayer.Children.Add(this); + AdornerLayer.SetAdornedElement(this, adornerLayer); + } } private void UpdatePseudoClasses(NotificationPosition position) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 21373c6b76..8ed6abd52b 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -869,7 +869,7 @@ namespace Avalonia.Controls } } - private static decimal OnCoerceMaximum(IAvaloniaObject instance, decimal value) + private static decimal OnCoerceMaximum(AvaloniaObject instance, decimal value) { if (instance is NumericUpDown upDown) { @@ -879,7 +879,7 @@ namespace Avalonia.Controls return value; } - private static decimal OnCoerceMinimum(IAvaloniaObject instance, decimal value) + private static decimal OnCoerceMinimum(AvaloniaObject instance, decimal value) { if (instance is NumericUpDown upDown) { @@ -889,7 +889,7 @@ namespace Avalonia.Controls return value; } - private static decimal OnCoerceIncrement(IAvaloniaObject instance, decimal value) + private static decimal OnCoerceIncrement(AvaloniaObject instance, decimal value) { if (instance is NumericUpDown upDown) { diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 2230b4b0d2..7134e1b9e7 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -16,7 +16,7 @@ namespace Avalonia.Controls /// Controls can be added to a by adding them to its /// collection. All children are layed out to fill the panel. /// - public class Panel : Control, IPanel, IChildIndexProvider + public class Panel : Control, IChildIndexProvider { /// /// Defines the property. @@ -84,7 +84,7 @@ namespace Avalonia.Controls /// /// The properties. protected static void AffectsParentArrange(params AvaloniaProperty[] properties) - where TPanel : class, IPanel + where TPanel : Panel { foreach (var property in properties) { @@ -97,7 +97,7 @@ namespace Avalonia.Controls /// /// The properties. protected static void AffectsParentMeasure(params AvaloniaProperty[] properties) - where TPanel : class, IPanel + where TPanel : Panel { foreach (var property in properties) { @@ -137,7 +137,7 @@ namespace Avalonia.Controls for (var i = 0; i < e.OldItems!.Count; ++i) { var index = i + e.OldStartingIndex; - var child = (IControl)e.NewItems![i]!; + var child = (Control)e.NewItems![i]!; LogicalChildren[index] = child; VisualChildren[index] = child; } @@ -157,24 +157,24 @@ namespace Avalonia.Controls } private static void AffectsParentArrangeInvalidate(AvaloniaPropertyChangedEventArgs e) - where TPanel : class, IPanel + where TPanel : Panel { - var control = e.Sender as IControl; + var control = e.Sender as Control; var panel = control?.VisualParent as TPanel; panel?.InvalidateArrange(); } private static void AffectsParentMeasureInvalidate(AvaloniaPropertyChangedEventArgs e) - where TPanel : class, IPanel + where TPanel : Panel { - var control = e.Sender as IControl; + var control = e.Sender as Control; var panel = control?.VisualParent as TPanel; panel?.InvalidateMeasure(); } int IChildIndexProvider.GetChildIndex(ILogical child) { - return child is IControl control ? Children.IndexOf(control) : -1; + return child is Control control ? Children.IndexOf(control) : -1; } public bool TryGetTotalCount(out int count) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 16aeb2f559..6d525da150 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -123,7 +123,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void GotFocus(object? sender, GotFocusEventArgs e) { - var item = GetMenuItem(e.Source as IControl); + var item = GetMenuItem(e.Source as Control); if (item?.Parent != null) { @@ -133,7 +133,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void LostFocus(object? sender, RoutedEventArgs e) { - var item = GetMenuItem(e.Source as IControl); + var item = GetMenuItem(e.Source as Control); if (item != null) { @@ -143,7 +143,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void KeyDown(object? sender, KeyEventArgs e) { - KeyDown(GetMenuItem(e.Source as IControl), e); + KeyDown(GetMenuItem(e.Source as Control), e); } protected internal virtual void KeyDown(IMenuItem? item, KeyEventArgs e) @@ -282,7 +282,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void AccessKeyPressed(object? sender, RoutedEventArgs e) { - var item = GetMenuItem(e.Source as IControl); + var item = GetMenuItem(e.Source as Control); if (item == null) { @@ -303,7 +303,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void PointerEntered(object? sender, PointerEventArgs e) { - var item = GetMenuItem(e.Source as IControl); + var item = GetMenuItem(e.Source as Control); if (item?.Parent == null) { @@ -349,7 +349,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void PointerMoved(object? sender, PointerEventArgs e) { // HACK: #8179 needs to be addressed to correctly implement it in the PointerPressed method. - var item = GetMenuItem(e.Source as IControl) as MenuItem; + var item = GetMenuItem(e.Source as Control) as MenuItem; if (item?.TransformedBounds == null) { return; @@ -364,7 +364,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void PointerExited(object? sender, PointerEventArgs e) { - var item = GetMenuItem(e.Source as IControl); + var item = GetMenuItem(e.Source as Control); if (item?.Parent == null) { @@ -399,9 +399,9 @@ namespace Avalonia.Controls.Platform protected internal virtual void PointerPressed(object? sender, PointerPressedEventArgs e) { - var item = GetMenuItem(e.Source as IControl); + var item = GetMenuItem(e.Source as Control); - if (sender is IVisual visual && + if (sender is Visual visual && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed && item?.HasSubMenu == true) { if (item.IsSubMenuOpen) @@ -430,7 +430,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void PointerReleased(object? sender, PointerReleasedEventArgs e) { - var item = GetMenuItem(e.Source as IControl); + var item = GetMenuItem(e.Source as Control); if (e.InitialPressMouseButton == MouseButton.Left && item?.HasSubMenu == false) { @@ -547,7 +547,7 @@ namespace Avalonia.Controls.Platform } } - protected static IMenuItem? GetMenuItem(IControl? item) + protected static IMenuItem? GetMenuItem(Control? item) { while (true) { diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 209d5f03dc..5b2356a7ce 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -61,7 +61,7 @@ namespace Avalonia.Platform _lastPosition = pt; RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData!, _allowedEffects, modifiers); - var tl = root.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + var tl = (root as Visual)?.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); tl?.PlatformImpl?.Input?.Invoke(rawEvent); var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers); @@ -69,7 +69,7 @@ namespace Avalonia.Platform return effect; } - private DragDropEffects GetPreferredEffect(DragDropEffects effect, RawInputModifiers modifiers) + private static DragDropEffects GetPreferredEffect(DragDropEffects effect, RawInputModifiers modifiers) { if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None) return effect; // No need to check for the modifiers. @@ -80,7 +80,7 @@ namespace Avalonia.Platform return DragDropEffects.Move; } - private StandardCursorType GetCursorForDropEffect(DragDropEffects effects) + private static StandardCursorType GetCursorForDropEffect(DragDropEffects effects) { if (effects.HasAllFlags(DragDropEffects.Copy)) return StandardCursorType.DragCopy; @@ -200,8 +200,8 @@ namespace Avalonia.Platform if (e.Root != _lastRoot) { - if (_lastRoot != null) - RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), e.InputModifiers); + if (_lastRoot is Visual lr && e.Root is Visual r) + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, lr.PointToClient(r.PointToScreen(e.Position)), e.InputModifiers); RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers); } else diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 87bbfce2a2..7b435e287d 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -155,7 +155,7 @@ namespace Avalonia.Controls.Presenters } } - protected override void PanelCreated(IPanel panel) + protected override void PanelCreated(Panel panel) { ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } @@ -171,8 +171,8 @@ namespace Avalonia.Controls.Presenters if (fromIndex != toIndex) { var generator = ItemContainerGenerator; - IControl? from = null; - IControl? to = null; + Control? from = null; + Control? to = null; if (fromIndex != -1) { @@ -208,7 +208,7 @@ namespace Avalonia.Controls.Presenters } } - private IControl? GetOrCreateContainer(int index) + private Control? GetOrCreateContainer(int index) { var container = ItemContainerGenerator.ContainerFromIndex(index); diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 39888c1239..584dfea97f 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -118,8 +118,8 @@ namespace Avalonia.Controls.Presenters /// /// Defines the property. /// - public static readonly DirectProperty ChildProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty ChildProperty = + AvaloniaProperty.RegisterDirect( nameof(Child), o => o.Child); @@ -161,7 +161,7 @@ namespace Avalonia.Controls.Presenters nameof(RecognizesAccessKey), cp => cp.RecognizesAccessKey, (cp, value) => cp.RecognizesAccessKey = value); - private IControl? _child; + private Control? _child; private bool _createdChild; private IRecyclingDataTemplate? _recyclingDataTemplate; private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); @@ -329,7 +329,7 @@ namespace Avalonia.Controls.Presenters /// /// Gets the control displayed by the presenter. /// - public IControl? Child + public Control? Child { get { return _child; } private set { SetAndRaise(ChildProperty, ref _child, value); } @@ -460,7 +460,7 @@ namespace Avalonia.Controls.Presenters } // Set the DataContext if the data isn't a control. - if (contentTemplate is { } || !(content is IControl)) + if (contentTemplate is { } || !(content is Control)) { DataContext = content; } @@ -544,16 +544,16 @@ namespace Avalonia.Controls.Presenters /// Creates the child control. /// /// The child control or null. - protected virtual IControl? CreateChild() + protected virtual Control? CreateChild() { var content = Content; var oldChild = Child; return CreateChild(content, oldChild, ContentTemplate); } - private IControl? CreateChild(object? content, IControl? oldChild, IDataTemplate? template) + private Control? CreateChild(object? content, Control? oldChild, IDataTemplate? template) { - var newChild = content as IControl; + var newChild = content as Control; // We want to allow creating Child from the Template, if Content is null. // But it's important to not use DataTemplates, otherwise we will break content presenters in many places, diff --git a/src/Avalonia.Controls/Presenters/IContentPresenter.cs b/src/Avalonia.Controls/Presenters/IContentPresenter.cs index 673de4700b..01aec3855f 100644 --- a/src/Avalonia.Controls/Presenters/IContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IContentPresenter.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.Presenters /// /// Gets the control displayed by the presenter. /// - IControl? Child { get; } + Control? Child { get; } /// /// Gets or sets the content to be displayed by the presenter. diff --git a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs index 562638e94a..4c1e16ef72 100644 --- a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs +++ b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls.Presenters /// interface. /// [NotClientImplementable] - public interface IContentPresenterHost : ITemplatedControl + public interface IContentPresenterHost { /// /// Gets a collection describing the logical children of the host control. diff --git a/src/Avalonia.Controls/Presenters/IItemsPresenter.cs b/src/Avalonia.Controls/Presenters/IItemsPresenter.cs index 7cc72ef0a7..dea8d4960e 100644 --- a/src/Avalonia.Controls/Presenters/IItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IItemsPresenter.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls.Presenters { IEnumerable? Items { get; set; } - IPanel? Panel { get; } + Panel? Panel { get; } void ItemsChanged(NotifyCollectionChangedEventArgs e); diff --git a/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs b/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs index db11474871..a73d2abb5f 100644 --- a/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs +++ b/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Presenters /// interface. /// [NotClientImplementable] - public interface IItemsPresenterHost : ITemplatedControl + public interface IItemsPresenterHost { /// /// Registers an with a host control. diff --git a/src/Avalonia.Controls/Presenters/IPresenter.cs b/src/Avalonia.Controls/Presenters/IPresenter.cs index 0399983189..dc1d40cdcd 100644 --- a/src/Avalonia.Controls/Presenters/IPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IPresenter.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Presenters /// of the presenter is not a part of the template. /// [NotClientImplementable] - public interface IPresenter : IControl, INamed + public interface IPresenter : INamed { } } diff --git a/src/Avalonia.Controls/Presenters/ItemContainerSync.cs b/src/Avalonia.Controls/Presenters/ItemContainerSync.cs index 81184f719e..7346a074f3 100644 --- a/src/Avalonia.Controls/Presenters/ItemContainerSync.cs +++ b/src/Avalonia.Controls/Presenters/ItemContainerSync.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls.Presenters } private static void RemoveContainers( - IPanel panel, + Panel panel, IEnumerable items) { foreach (var i in items) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index 4c412e9a41..1f0f2a2f67 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs @@ -32,7 +32,7 @@ namespace Avalonia.Controls.Presenters if (panel != null) { - _subscriptions = panel.GetObservable(Panel.BoundsProperty) + _subscriptions = ((AvaloniaObject)panel).GetObservable(Panel.BoundsProperty) .Skip(1) .Subscribe(_ => InvalidateScroll()); } @@ -254,7 +254,7 @@ namespace Avalonia.Controls.Presenters /// The movement direction. /// The control from which movement begins. /// The control. - public virtual IControl? GetControlInDirection(NavigationDirection direction, IControl? from) + public virtual Control? GetControlInDirection(NavigationDirection direction, Control? from) { return null; } diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 4db2ec6d50..f010c46c1a 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -226,7 +226,7 @@ namespace Avalonia.Controls.Presenters InvalidateScroll(); } - public override IControl? GetControlInDirection(NavigationDirection direction, IControl? from) + public override Control? GetControlInDirection(NavigationDirection direction, Control? from) { var generator = Owner.ItemContainerGenerator; var panel = VirtualizingPanel; @@ -313,8 +313,9 @@ namespace Avalonia.Controls.Presenters { var generator = Owner.ItemContainerGenerator; var panel = VirtualizingPanel; + var panelControl = (Control)panel; - if (!panel.IsFull && Items != null && panel.IsAttachedToVisualTree) + if (!panel.IsFull && Items != null && panelControl.IsAttachedToVisualTree) { var index = NextIndex; var step = 1; @@ -513,25 +514,26 @@ namespace Avalonia.Controls.Presenters /// /// The item index. /// The container that was brought into view. - private IControl? ScrollIntoViewCore(int index) + private Control? ScrollIntoViewCore(int index) { var panel = VirtualizingPanel; + var panelControl = (Control)panel; var generator = Owner.ItemContainerGenerator; var newOffset = -1.0; //better not trigger any container generation/recycle while or layout stuff //before panel is attached/visible - if (!panel.IsAttachedToVisualTree) + if (!panelControl.IsAttachedToVisualTree) { return null; } - if (!panel.IsMeasureValid && panel.PreviousMeasure.HasValue) + if (!panelControl.IsMeasureValid && panelControl.PreviousMeasure.HasValue) { //before any kind of scrolling we need to make sure panel measure is valid //or we risk get panel into not valid state //we make a preemptive quick measure so scrolling is valid - panel.Measure(panel.PreviousMeasure.Value); + panelControl.Measure(panelControl.PreviousMeasure.Value); } if (index >= 0 && index < ItemCount) @@ -567,17 +569,17 @@ namespace Avalonia.Controls.Presenters { OffsetValue = newOffset; } - + if (panel.ScrollDirection == Orientation.Vertical) { - if (container.Bounds.Y < panel.Bounds.Y || container.Bounds.Bottom > panel.Bounds.Bottom) + if (container.Bounds.Y < panelControl.Bounds.Y || container.Bounds.Bottom > panelControl.Bounds.Bottom) { OffsetValue += 1; } } else { - if (container.Bounds.X < panel.Bounds.X || container.Bounds.Right > panel.Bounds.Right) + if (container.Bounds.X < panelControl.Bounds.X || container.Bounds.Right > panelControl.Bounds.Right) { OffsetValue += 1; } diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 265704ceaa..924c4567f6 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -111,13 +111,13 @@ namespace Avalonia.Controls.Presenters internal ItemVirtualizer? Virtualizer { get; private set; } /// - bool ILogicalScrollable.BringIntoView(IControl target, Rect targetRect) + bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect) { return false; } /// - IControl? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, IControl? from) + Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from) { return Virtualizer?.GetControlInDirection(direction, from); } @@ -145,7 +145,7 @@ namespace Avalonia.Controls.Presenters } /// - protected override void PanelCreated(IPanel panel) + protected override void PanelCreated(Panel panel) { Virtualizer?.Dispose(); Virtualizer = ItemVirtualizer.Create(this); diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 836433cdf8..38a2a5722c 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Presenters /// /// Base class for controls that present items inside an . /// - public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedControl, IChildIndexProvider + public abstract class ItemsPresenterBase : Control, IItemsPresenter, IChildIndexProvider { /// /// Defines the property. @@ -25,7 +25,7 @@ namespace Avalonia.Controls.Presenters /// /// Defines the property. /// - public static readonly StyledProperty> ItemsPanelProperty = + public static readonly StyledProperty> ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(); /// @@ -112,7 +112,7 @@ namespace Avalonia.Controls.Presenters /// /// Gets or sets a template which creates the used to display the items. /// - public ITemplate ItemsPanel + public ITemplate ItemsPanel { get { return GetValue(ItemsPanelProperty); } set { SetValue(ItemsPanelProperty, value); } @@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters /// /// Gets the panel used to display the items. /// - public IPanel? Panel + public Panel? Panel { get; private set; @@ -229,7 +229,7 @@ namespace Avalonia.Controls.Presenters /// Called when the is created. /// /// The panel. - protected virtual void PanelCreated(IPanel panel) + protected virtual void PanelCreated(Panel panel) { } @@ -290,7 +290,7 @@ namespace Avalonia.Controls.Presenters int IChildIndexProvider.GetChildIndex(ILogical child) { - if (child is IControl control && ItemContainerGenerator is { } generator) + if (child is Control control && ItemContainerGenerator is { } generator) { var index = ItemContainerGenerator.IndexFromContainer(control); diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 189197dd58..328facba0b 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -73,8 +73,8 @@ namespace Avalonia.Controls.Presenters private IDisposable? _logicalScrollSubscription; private Size _viewport; private Dictionary? _activeLogicalGestureScrolls; - private List? _anchorCandidates; - private IControl? _anchorElement; + private List? _anchorCandidates; + private Control? _anchorElement; private Rect _anchorElementBounds; private bool _isAnchorElementDirty; @@ -158,7 +158,7 @@ namespace Avalonia.Controls.Presenters } /// - IControl? IScrollAnchorProvider.CurrentAnchor + Control? IScrollAnchorProvider.CurrentAnchor { get { @@ -173,7 +173,7 @@ namespace Avalonia.Controls.Presenters /// The target visual. /// The portion of the target visual to bring into view. /// True if the scroll offset was changed; otherwise false. - public bool BringDescendantIntoView(IVisual target, Rect targetRect) + public bool BringDescendantIntoView(Visual target, Rect targetRect) { if (Child?.IsEffectivelyVisible != true) { @@ -181,7 +181,7 @@ namespace Avalonia.Controls.Presenters } var scrollable = Child as ILogicalScrollable; - var control = target as IControl; + var control = target as Control; if (scrollable?.IsLogicalScrollEnabled == true && control != null) { @@ -232,7 +232,7 @@ namespace Avalonia.Controls.Presenters } /// - void IScrollAnchorProvider.RegisterAnchorCandidate(IControl element) + void IScrollAnchorProvider.RegisterAnchorCandidate(Control element) { if (!this.IsVisualAncestorOf(element)) { @@ -240,13 +240,13 @@ namespace Avalonia.Controls.Presenters "An anchor control must be a visual descendent of the ScrollContentPresenter."); } - _anchorCandidates ??= new List(); + _anchorCandidates ??= new List(); _anchorCandidates.Add(element); _isAnchorElementDirty = true; } /// - void IScrollAnchorProvider.UnregisterAnchorCandidate(IControl element) + void IScrollAnchorProvider.UnregisterAnchorCandidate(Control element) { _anchorCandidates?.Remove(element); _isAnchorElementDirty = true; @@ -496,7 +496,7 @@ namespace Avalonia.Controls.Presenters private void ChildChanged(AvaloniaPropertyChangedEventArgs e) { - UpdateScrollableSubscription((IControl?)e.NewValue); + UpdateScrollableSubscription((Control?)e.NewValue); if (e.OldValue != null) { @@ -504,7 +504,7 @@ namespace Avalonia.Controls.Presenters } } - private void UpdateScrollableSubscription(IControl? child) + private void UpdateScrollableSubscription(Control? child) { var scrollable = child as ILogicalScrollable; @@ -564,7 +564,7 @@ namespace Avalonia.Controls.Presenters _anchorElementBounds = default; _isAnchorElementDirty = false; - var bestCandidate = default(IControl); + var bestCandidate = default(Control); var bestCandidateDistance = double.MaxValue; // Find the anchor candidate that is scrolled closest to the top-left of this @@ -595,7 +595,7 @@ namespace Avalonia.Controls.Presenters } } - private bool GetViewportBounds(IControl element, out Rect bounds) + private bool GetViewportBounds(Control element, out Rect bounds) { if (TranslateBounds(element, Child!, out var childBounds)) { @@ -612,7 +612,7 @@ namespace Avalonia.Controls.Presenters return false; } - private Rect TranslateBounds(IControl control, IControl to) + private Rect TranslateBounds(Control control, Control to) { if (TranslateBounds(control, to, out var bounds)) { @@ -622,7 +622,7 @@ namespace Avalonia.Controls.Presenters throw new InvalidOperationException("The control's bounds could not be translated to the requested control."); } - private bool TranslateBounds(IControl control, IControl to, out Rect bounds) + private bool TranslateBounds(Control control, Control to, out Rect bounds) { if (!control.IsVisible) { diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index d557424fbb..89abe1cdaa 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -60,7 +60,7 @@ namespace Avalonia.Controls.Primitives adorner.SetValue(AdornedElementProperty, adorned); } - public static AdornerLayer? GetAdornerLayer(IVisual visual) + public static AdornerLayer? GetAdornerLayer(Visual visual) { return visual.FindAncestorOfType()?.AdornerLayer; } @@ -232,7 +232,7 @@ namespace Avalonia.Controls.Primitives layer?.UpdateAdornedElement(adorner, adorned); } - private void UpdateClip(IControl control, TransformedBounds bounds, bool isEnabled) + private void UpdateClip(Control control, TransformedBounds bounds, bool isEnabled) { if (!isEnabled) { diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs index a774368af6..20b3b849a6 100644 --- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs @@ -6,7 +6,7 @@ namespace Avalonia.Controls.Primitives { public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest { - public static Panel? GetOverlayLayer(IVisual visual) + public static Panel? GetOverlayLayer(Visual visual) { foreach (var v in visual.GetVisualAncestors()) if (v is VisualLayerManager vlm) diff --git a/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs b/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs index 0b3791be3a..e0c6241620 100644 --- a/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs +++ b/src/Avalonia.Controls/Primitives/ILogicalScrollable.cs @@ -56,7 +56,7 @@ namespace Avalonia.Controls.Primitives /// The target visual. /// The portion of the target visual to bring into view. /// True if the scroll offset was changed; otherwise false. - bool BringIntoView(IControl target, Rect targetRect); + bool BringIntoView(Control target, Rect targetRect); /// /// Gets the next control in the specified direction. @@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives /// The movement direction. /// The control from which movement begins. /// The control. - IControl? GetControlInDirection(NavigationDirection direction, IControl? from); + Control? GetControlInDirection(NavigationDirection direction, Control? from); /// /// Raises the event. diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index bd2aa39621..90fbdcdbec 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -68,7 +68,7 @@ namespace Avalonia.Controls.Primitives /// Gets the root of the visual tree in the case where the popup is presented using a /// separate visual tree. /// - IVisual? HostedVisualTreeRoot { get; } + Visual? HostedVisualTreeRoot { get; } /// /// Raised when the control's template is applied. @@ -88,7 +88,7 @@ namespace Avalonia.Controls.Primitives /// /// The anchor rect. If null, the bounds of will be used. /// - void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, + void ConfigurePosition(Visual target, PlacementMode placement, Point offset, PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None, PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, @@ -98,7 +98,7 @@ namespace Avalonia.Controls.Primitives /// Sets the control to display in the popup. /// /// - void SetChild(IControl? control); + void SetChild(Control? control); /// /// Shows the popup. diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 04c30b0f33..8525f025a3 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -26,7 +26,7 @@ namespace Avalonia.Controls.Primitives /// /// The visual. /// The light dismiss overlay, or null if none found. - public static LightDismissOverlayLayer? GetLightDismissOverlayLayer(IVisual visual) + public static LightDismissOverlayLayer? GetLightDismissOverlayLayer(Visual visual) { visual = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -48,13 +48,13 @@ namespace Avalonia.Controls.Primitives public bool HitTest(Point point) { - if (InputPassThroughElement is object) + if (InputPassThroughElement is Visual v) { - var hit = VisualRoot?.GetVisualAt(point, x => x != this); + var hit = ((Visual?)VisualRoot)?.GetVisualAt(point, x => x != this); if (hit is object) { - return !InputPassThroughElement.IsVisualAncestorOf(hit); + return !v.IsVisualAncestorOf(hit); } } diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs index 468879edd1..91136cb295 100644 --- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs @@ -7,7 +7,7 @@ namespace Avalonia.Controls.Primitives public class OverlayLayer : Canvas, ICustomSimpleHitTest { public Size AvailableSize { get; private set; } - public static OverlayLayer? GetOverlayLayer(IVisual visual) + public static OverlayLayer? GetOverlayLayer(Visual visual) { foreach(var v in visual.GetVisualAncestors()) if(v is VisualLayerManager vlm) diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index 4765718c3b..cd26ea4f6e 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -9,7 +9,7 @@ using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { - public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup + public class OverlayPopupHost : ContentControl, IPopupHost, IManagedPopupPositionerPopup { /// /// Defines the property. @@ -29,12 +29,12 @@ namespace Avalonia.Controls.Primitives _positioner = new ManagedPopupPositioner(this); } - public void SetChild(IControl? control) + public void SetChild(Control? control) { Content = control; } - public IVisual? HostedVisualTreeRoot => null; + public Visual? HostedVisualTreeRoot => null; public Transform? Transform { @@ -42,15 +42,14 @@ namespace Avalonia.Controls.Primitives set => SetValue(TransformProperty, value); } - /// - IInteractive? IInteractive.InteractiveParent => Parent; - bool IPopupHost.Topmost { get => false; set { /* Not currently supported in overlay popups */ } } + protected internal override Interactive? InteractiveParent => Parent; + public void Dispose() => Hide(); @@ -66,7 +65,7 @@ namespace Avalonia.Controls.Primitives _shown = false; } - public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, + public void ConfigurePosition(Visual target, PlacementMode placement, Point offset, PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None, PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, Rect? rect = null) @@ -122,7 +121,7 @@ namespace Avalonia.Controls.Primitives double IManagedPopupPositionerPopup.Scaling => 1; - public static IPopupHost CreatePopupHost(IVisual target, IAvaloniaDependencyResolver? dependencyResolver) + public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver) { var platform = (target.GetVisualRoot() as TopLevel)?.PlatformImpl?.CreatePopup(); if (platform != null) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index ccb81ba276..3c329a9a3e 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.Linq; using System.Reactive.Disposables; using Avalonia.Automation.Peers; using Avalonia.Controls.Mixins; @@ -15,7 +14,6 @@ using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.VisualTree; using Avalonia.Media; -using Avalonia.Utilities; namespace Avalonia.Controls.Primitives { @@ -352,7 +350,7 @@ namespace Avalonia.Controls.Primitives return; } - var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType(); + var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType(); if (placementTarget == null) { @@ -546,7 +544,7 @@ namespace Avalonia.Controls.Primitives { if (change.Property == PlacementTargetProperty) { - var newTarget = change.GetNewValue() ?? this.FindLogicalAncestorOfType(); + var newTarget = change.GetNewValue() ?? this.FindLogicalAncestorOfType(); if (newTarget is null || newTarget.GetVisualRoot() != _openState.TopLevel) { @@ -566,7 +564,7 @@ namespace Avalonia.Controls.Primitives } } - private void UpdateHostPosition(IPopupHost popupHost, IControl placementTarget) + private void UpdateHostPosition(IPopupHost popupHost, Control placementTarget) { popupHost.ConfigurePosition( placementTarget, @@ -578,7 +576,7 @@ namespace Avalonia.Controls.Primitives PlacementRect ?? new Rect(default, placementTarget.Bounds.Size)); } - private void UpdateHostSizing(IPopupHost popupHost, TopLevel topLevel, IControl placementTarget) + private void UpdateHostSizing(IPopupHost popupHost, TopLevel topLevel, Control placementTarget) { var scaleX = 1.0; var scaleY = 1.0; @@ -613,7 +611,7 @@ namespace Avalonia.Controls.Primitives { if (_openState != null) { - var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType(); + var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType(); if (placementTarget == null) return; _openState.PopupHost.ConfigurePosition( @@ -639,7 +637,7 @@ namespace Avalonia.Controls.Primitives return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler)); } - private void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint) + private static void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint) { if(host is PopupRoot pr && pr.PlatformImpl is not null) { @@ -723,7 +721,7 @@ namespace Avalonia.Controls.Primitives { if (PlacementTarget != null) { - var e = (IControl?)PlacementTarget; + var e = (Control?)PlacementTarget; while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible)) { @@ -737,7 +735,7 @@ namespace Avalonia.Controls.Primitives } else { - var anc = this.FindLogicalAncestorOfType(); + var anc = this.FindLogicalAncestorOfType(); if (anc != null) { FocusManager.Instance?.Focus(anc); @@ -758,7 +756,7 @@ namespace Avalonia.Controls.Primitives private void PointerPressedDismissOverlay(object? sender, PointerPressedEventArgs e) { - if (IsLightDismissEnabled && e.Source is IVisual v && !IsChildOrThis(v)) + if (IsLightDismissEnabled && e.Source is Visual v && !IsChildOrThis(v)) { CloseCore(); @@ -769,10 +767,10 @@ namespace Avalonia.Controls.Primitives } } - private void PassThroughEvent(PointerPressedEventArgs e) + private static void PassThroughEvent(PointerPressedEventArgs e) { if (e.Source is LightDismissOverlayLayer layer && - layer.GetVisualRoot() is IInputElement root) + layer.GetVisualRoot() is InputElement root) { var p = e.GetCurrentPoint(root); var hit = root.InputHitTest(p.Position, x => x != layer); @@ -802,18 +800,18 @@ namespace Avalonia.Controls.Primitives // If the Popup appears in a control template, then the child controls // that appear in the popup host need to have their TemplatedParent // properties set. - if (TemplatedParent != null && popupHost.Presenter != null) + if (TemplatedParent != null && popupHost.Presenter is Control presenter) { - popupHost.Presenter.ApplyTemplate(); + presenter.ApplyTemplate(); - var presenterSubscription = popupHost.Presenter.GetObservable(ContentPresenter.ChildProperty) + var presenterSubscription = presenter.GetObservable(ContentPresenter.ChildProperty) .Subscribe(SetTemplatedParentAndApplyChildTemplates); _openState.SetPresenterSubscription(presenterSubscription); } } - private void SetTemplatedParentAndApplyChildTemplates(IControl? control) + private void SetTemplatedParentAndApplyChildTemplates(Control? control) { if (control != null) { @@ -821,7 +819,7 @@ namespace Avalonia.Controls.Primitives } } - private bool IsChildOrThis(IVisual child) + private bool IsChildOrThis(Visual child) { if (_openState is null) { @@ -830,7 +828,7 @@ namespace Avalonia.Controls.Primitives var popupHost = _openState.PopupHost; - IVisual? root = child.VisualRoot; + Visual? root = child.VisualRoot as Visual; while (root is IHostedVisualTreeRoot hostedRoot) { @@ -839,13 +837,13 @@ namespace Avalonia.Controls.Primitives return true; } - root = hostedRoot.Host?.VisualRoot; + root = hostedRoot.Host?.VisualRoot as Visual; } return false; } - public bool IsInsidePopup(IVisual visual) + public bool IsInsidePopup(Visual visual) { if (_openState is null) { @@ -854,7 +852,7 @@ namespace Avalonia.Controls.Primitives var popupHost = _openState.PopupHost; - return popupHost != null && ((IVisual)popupHost).IsVisualAncestorOf(visual); + return popupHost != null && ((Visual)popupHost).IsVisualAncestorOf(visual); } public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false; @@ -921,7 +919,7 @@ namespace Avalonia.Controls.Primitives private readonly IDisposable _cleanup; private IDisposable? _presenterCleanup; - public PopupOpenState(IControl placementTarget, TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup) + public PopupOpenState(Control placementTarget, TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup) { PlacementTarget = placementTarget; TopLevel = topLevel; @@ -930,7 +928,7 @@ namespace Avalonia.Controls.Primitives } public TopLevel TopLevel { get; } - public IControl PlacementTarget { get; set; } + public Control PlacementTarget { get; set; } public IPopupHost PopupHost { get; } public void SetPresenterSubscription(IDisposable? presenterCleanup) diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index 615eb69fe3..17dfec118f 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -448,7 +448,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning { public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters, TopLevel topLevel, - IVisual target, PlacementMode placement, Point offset, + Visual target, PlacementMode placement, Point offset, PopupAnchor anchor, PopupGravity gravity, PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect, FlowDirection flowDirection) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index f7bf7c1a27..57ec864cad 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives /// /// The root window of a . /// - public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost + public sealed class PopupRoot : WindowBase, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost { /// /// Defines the property. @@ -72,12 +72,12 @@ namespace Avalonia.Controls.Primitives /// /// Popup events are passed to their parent window. This facilitates this. /// - IInteractive? IInteractive.InteractiveParent => Parent; + protected internal override Interactive? InteractiveParent => Parent; /// /// Gets the control that is hosting the popup root. /// - IVisual? IHostedVisualTreeRoot.Host => Parent; + Visual? IHostedVisualTreeRoot.Host => Parent; /// /// Gets the styling parent of the popup root. @@ -98,7 +98,7 @@ namespace Avalonia.Controls.Primitives PlatformImpl?.PopupPositioner.Update(_positionerParameters); } - public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, + public void ConfigurePosition(Visual target, PlacementMode placement, Point offset, PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None, PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, @@ -111,9 +111,9 @@ namespace Avalonia.Controls.Primitives UpdatePosition(); } - public void SetChild(IControl? control) => Content = control; + public void SetChild(Control? control) => Content = control; - IVisual IPopupHost.HostedVisualTreeRoot => this; + Visual IPopupHost.HostedVisualTreeRoot => this; protected override Size MeasureOverride(Size availableSize) { diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index e03b02a479..44fa78ac21 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -386,11 +386,11 @@ namespace Avalonia.Controls.Primitives /// /// The control that raised the event. /// The container or null if the event did not originate in a container. - protected IControl? GetContainerFromEventSource(IInteractive? eventSource) + protected Control? GetContainerFromEventSource(object? eventSource) { - for (var current = eventSource as IVisual; current != null; current = current.VisualParent) + for (var current = eventSource as Visual; current != null; current = current.VisualParent) { - if (current is IControl control && control.LogicalParent == this && + if (current is Control control && control.Parent == this && ItemContainerGenerator?.IndexFromContainer(control) != -1) { return control; @@ -588,7 +588,7 @@ namespace Avalonia.Controls.Primitives } else if (e.Key == Key.Space || e.Key == Key.Enter) { - e.Handled = UpdateSelectionFromEventSource( + UpdateSelectionFromEventSource( e.Source, true, e.KeyModifiers.HasFlag(KeyModifiers.Shift), @@ -645,10 +645,10 @@ namespace Avalonia.Controls.Primitives /// The direction to move. /// Whether to wrap when the selection reaches the first or last item. /// True if the selection was moved; otherwise false. - protected bool MoveSelection(IControl? from, NavigationDirection direction, bool wrap) + protected bool MoveSelection(Control? from, NavigationDirection direction, bool wrap) { if (Presenter?.Panel is INavigableContainer container && - GetNextControl(container, direction, from, wrap) is IControl next) + GetNextControl(container, direction, from, wrap) is Control next) { var index = ItemContainerGenerator?.IndexFromContainer(next) ?? -1; @@ -750,7 +750,7 @@ namespace Avalonia.Controls.Primitives /// Whether the event is a right-click. /// Wheter the event is a focus event protected void UpdateSelection( - IControl container, + Control container, bool select = true, bool rangeModifier = false, bool toggleModifier = false, @@ -780,7 +780,7 @@ namespace Avalonia.Controls.Primitives /// false. /// protected bool UpdateSelectionFromEventSource( - IInteractive? eventSource, + object? eventSource, bool select = true, bool rangeModifier = false, bool toggleModifier = false, @@ -892,7 +892,7 @@ namespace Avalonia.Controls.Primitives !_hasScrolledToSelectedItem && Presenter is object && Selection.AnchorIndex >= 0 && - ((IVisual)this).IsAttachedToVisualTree) + IsAttachedToVisualTree) { ScrollIntoView(Selection.AnchorIndex); _hasScrolledToSelectedItem = true; @@ -906,9 +906,9 @@ namespace Avalonia.Controls.Primitives private void ContainerSelectionChanged(RoutedEventArgs e) { if (!_ignoreContainerSelectionChanged && - e.Source is IControl control && + e.Source is Control control && e.Source is ISelectable selectable && - control.LogicalParent == this && + control.Parent == this && ItemContainerGenerator?.IndexFromContainer(control) != -1) { UpdateSelection(control, selectable.IsSelected); @@ -926,7 +926,7 @@ namespace Avalonia.Controls.Primitives /// The container. /// Whether the control is selected /// The previous selection state. - private bool MarkContainerSelected(IControl container, bool selected) + private bool MarkContainerSelected(Control container, bool selected) { try { @@ -955,7 +955,7 @@ namespace Avalonia.Controls.Primitives private void UpdateContainerSelection() { - if (Presenter?.Panel is IPanel panel) + if (Presenter?.Panel is Panel panel) { foreach (var container in panel.Children) { diff --git a/src/Avalonia.Controls/Primitives/TabStrip.cs b/src/Avalonia.Controls/Primitives/TabStrip.cs index f8f7674da8..d27b1faaed 100644 --- a/src/Avalonia.Controls/Primitives/TabStrip.cs +++ b/src/Avalonia.Controls/Primitives/TabStrip.cs @@ -8,8 +8,8 @@ namespace Avalonia.Controls.Primitives { public class TabStrip : SelectingItemsControl { - private static readonly FuncTemplate DefaultPanel = - new FuncTemplate(() => new WrapPanel { Orientation = Orientation.Horizontal }); + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new WrapPanel { Orientation = Orientation.Horizontal }); static TabStrip() { @@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives { base.OnPointerPressed(e); - if (e.Source is IVisual source) + if (e.Source is Visual source) { var point = e.GetCurrentPoint(source); diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 17d90e6dd0..9a684c4534 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives /// /// A lookless control whose visual appearance is defined by its . /// - public class TemplatedControl : Control, ITemplatedControl + public class TemplatedControl : Control { /// /// Defines the property. @@ -307,7 +307,7 @@ namespace Avalonia.Controls.Primitives } /// - protected override IControl GetTemplateFocusTarget() + protected override Control GetTemplateFocusTarget() { foreach (Control child in this.GetTemplateChildren()) { @@ -380,7 +380,7 @@ namespace Avalonia.Controls.Primitives /// /// The control. /// The templated parent to apply. - internal static void ApplyTemplatedParent(IStyledElement control, ITemplatedControl? templatedParent) + internal static void ApplyTemplatedParent(StyledElement control, AvaloniaObject? templatedParent) { control.SetValue(TemplatedParentProperty, templatedParent); @@ -389,7 +389,7 @@ namespace Avalonia.Controls.Primitives for (var i = 0; i < count; i++) { - if (children[i] is IStyledElement child && child.TemplatedParent is null) + if (children[i] is StyledElement child && child.TemplatedParent is null) { ApplyTemplatedParent(child, templatedParent); } diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 71a7a58da4..165bec3a95 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -270,15 +270,16 @@ namespace Avalonia.Controls double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum); // When the Orientation changed, the indicator's Width or Height should set to double.NaN. + // Indicator size calculation should consider the ProgressBar's Padding property setting if (Orientation == Orientation.Horizontal) { - _indicator.Width = barSize.Width * percent; + _indicator.Width = (barSize.Width - _indicator.Margin.Left - _indicator.Margin.Right) * percent; _indicator.Height = double.NaN; } else { _indicator.Width = double.NaN; - _indicator.Height = barSize.Height * percent; + _indicator.Height = (barSize.Height - _indicator.Margin.Top - _indicator.Margin.Bottom) * percent; } diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index ab8e064028..af466eea48 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls if (dependency is Layoutable layoutable) { - if (Children.Contains((ILayoutable)layoutable)) + if (Children.Contains(layoutable)) return layoutable; throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 3bc0593813..bc11c35fde 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -258,7 +258,7 @@ namespace Avalonia.Controls.Remote.Server protected virtual Size Measure(Size constraint) { - var l = (ILayoutable) InputRoot!; + var l = (Layoutable) InputRoot!; l.Measure(constraint); return l.DesiredSize; } diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls/Repeater/ElementFactory.cs index 6b776803d7..7862892943 100644 --- a/src/Avalonia.Controls/Repeater/ElementFactory.cs +++ b/src/Avalonia.Controls/Repeater/ElementFactory.cs @@ -4,12 +4,12 @@ namespace Avalonia.Controls { public abstract class ElementFactory : IElementFactory { - public IControl Build(object? data) + public Control Build(object? data) { return GetElementCore(new ElementFactoryGetArgs { Data = data }); } - public IControl GetElement(ElementFactoryGetArgs args) + public Control GetElement(ElementFactoryGetArgs args) { return GetElementCore(args); } @@ -21,7 +21,7 @@ namespace Avalonia.Controls RecycleElementCore(args); } - protected abstract IControl GetElementCore(ElementFactoryGetArgs args); + protected abstract Control GetElementCore(ElementFactoryGetArgs args); protected abstract void RecycleElementCore(ElementFactoryRecycleArgs args); } } diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs index daab7e855f..16f93695de 100644 --- a/src/Avalonia.Controls/Repeater/IElementFactory.cs +++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs @@ -15,10 +15,10 @@ namespace Avalonia.Controls public object? Data { get; set; } /// - /// Gets or sets the that is expected to be the parent of the + /// Gets or sets the that is expected to be the parent of the /// realized element from . /// - public IControl? Parent { get; set; } + public Control? Parent { get; set; } /// /// Gets or sets the index of the item that should be realized. @@ -33,16 +33,16 @@ namespace Avalonia.Controls public class ElementFactoryRecycleArgs { /// - /// Gets or sets the to recycle when calling + /// Gets or sets the to recycle when calling /// . /// - public IControl? Element { get; set; } + public Control? Element { get; set; } /// - /// Gets or sets the that is expected to be the parent of the + /// Gets or sets the that is expected to be the parent of the /// realized element from . /// - public IControl? Parent { get; set; } + public Control? Parent { get; set; } } /// @@ -51,13 +51,13 @@ namespace Avalonia.Controls public interface IElementFactory : IDataTemplate { /// - /// Gets an . + /// Gets an . /// /// The element args. - public IControl GetElement(ElementFactoryGetArgs args); + public Control GetElement(ElementFactoryGetArgs args); /// - /// Recycles an that was previously retrieved using + /// Recycles an that was previously retrieved using /// . /// /// The recycle args. diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs index eb3b2ea787..c25d15a27b 100644 --- a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs +++ b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs @@ -13,10 +13,10 @@ namespace Avalonia.Controls public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate; - public IControl Build(object? param) => GetElement(null, param); + public Control Build(object? param) => GetElement(null, param); public bool Match(object? data) => _dataTemplate.Match(data); - public IControl GetElement(ElementFactoryGetArgs args) + public Control GetElement(ElementFactoryGetArgs args) { return GetElement(args.Parent, args.Data); } @@ -26,11 +26,11 @@ namespace Avalonia.Controls RecycleElement(args.Parent, args.Element!); } - private IControl GetElement(IControl? parent, object? data) + private Control GetElement(Control? parent, object? data) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); - IControl? element = null; + Control? element = null; if (recyclePool != null) { @@ -50,7 +50,7 @@ namespace Avalonia.Controls return element; } - private void RecycleElement(IControl? parent, IControl element) + private void RecycleElement(Control? parent, Control element) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index ae663defd3..41a0dc384f 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(VerticalCacheLength), 2.0); private static readonly StyledProperty VirtualizationInfoProperty = - AvaloniaProperty.RegisterAttached("VirtualizationInfo"); + AvaloniaProperty.RegisterAttached("VirtualizationInfo"); internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1); internal static readonly Point ClearedElementsArrangePosition = new Point(-10000.0, -10000.0); @@ -156,9 +156,9 @@ namespace Avalonia.Controls internal IElementFactory? ItemTemplateShim { get; set; } internal Point LayoutOrigin { get; set; } internal object? LayoutState { get; set; } - internal IControl? MadeAnchor => _viewportManager.MadeAnchor; + internal Control? MadeAnchor => _viewportManager.MadeAnchor; internal Rect RealizationWindow => _viewportManager.GetLayoutRealizationWindow(); - internal IControl? SuggestedAnchor => _viewportManager.SuggestedAnchor; + internal Control? SuggestedAnchor => _viewportManager.SuggestedAnchor; private bool IsProcessingCollectionChange => _processingItemsSourceChange != null; @@ -183,7 +183,7 @@ namespace Avalonia.Controls int IChildIndexProvider.GetChildIndex(ILogical child) { - return child is IControl control + return child is Control control ? GetElementIndex(control) : -1; } @@ -205,7 +205,7 @@ namespace Avalonia.Controls public event EventHandler? ElementClearing; /// - /// Occurs for each realized when the index for the item it + /// Occurs for each realized when the index for the item it /// represents has changed. /// /// @@ -213,7 +213,7 @@ namespace Avalonia.Controls /// interactions on the child elements (such as selection or click), it is useful to be /// able to keep an up-to-date identifier for the backing data item. /// - /// This event is raised for each realized IControl where the index for the item it + /// This event is raised for each realized Control where the index for the item it /// represents has changed. For example, when another item is added or removed in the data /// source, the index for items that come after in the ordering will be impacted. /// @@ -230,7 +230,7 @@ namespace Avalonia.Controls /// /// Retrieves the index of the item from the data source that corresponds to the specified - /// . + /// . /// /// /// The element that corresponds to the item to get the index of. @@ -239,7 +239,7 @@ namespace Avalonia.Controls /// The index of the item from the data source that corresponds to the specified UIElement, /// or -1 if the element is not supported. /// - public int GetElementIndex(IControl element) => GetElementIndexImpl(element); + public int GetElementIndex(Control element) => GetElementIndexImpl(element); /// /// Retrieves the realized UIElement that corresponds to the item at the specified index in @@ -250,7 +250,7 @@ namespace Avalonia.Controls /// he UIElement that corresponds to the item at the specified index if the item is /// realized, or null if the item is not realized. /// - public IControl? TryGetElement(int index) => GetElementFromIndexImpl(index); + public Control? TryGetElement(int index) => GetElementFromIndexImpl(index); /// /// Retrieves the UIElement that corresponds to the item at the specified index in the @@ -258,21 +258,21 @@ namespace Avalonia.Controls /// /// The index of the item. /// - /// An that corresponds to the item at the specified index. If the + /// An that corresponds to the item at the specified index. If the /// item is not realized, a new UIElement is created. /// - public IControl GetOrCreateElement(int index) => GetOrCreateElementImpl(index); + public Control GetOrCreateElement(int index) => GetOrCreateElementImpl(index); - internal void PinElement(IControl element) => _viewManager.UpdatePin(element, true); + internal void PinElement(Control element) => _viewManager.UpdatePin(element, true); - internal void UnpinElement(IControl element) => _viewManager.UpdatePin(element, false); + internal void UnpinElement(Control element) => _viewManager.UpdatePin(element, false); - internal static VirtualizationInfo? TryGetVirtualizationInfo(IControl element) + internal static VirtualizationInfo? TryGetVirtualizationInfo(Control element) { return (element as AvaloniaObject)?.GetValue(VirtualizationInfoProperty); } - internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(IControl element) + internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(Control element) { if (TryGetVirtualizationInfo(element) != null) { @@ -284,7 +284,7 @@ namespace Avalonia.Controls return result; } - internal static VirtualizationInfo GetVirtualizationInfo(IControl element) + internal static VirtualizationInfo GetVirtualizationInfo(Control element) { if (element is AvaloniaObject ao) { @@ -299,7 +299,7 @@ namespace Avalonia.Controls return result; } - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + throw new NotSupportedException("Custom implementations of AvaloniaObject not supported."); } private protected override void InvalidateMeasureOnChildrenChanged() @@ -470,13 +470,13 @@ namespace Avalonia.Controls base.OnPropertyChanged(change); } - internal IControl GetElementImpl(int index, bool forceCreate, bool suppressAutoRecycle) + internal Control GetElementImpl(int index, bool forceCreate, bool suppressAutoRecycle) { var element = _viewManager.GetElement(index, forceCreate, suppressAutoRecycle); return element; } - internal void ClearElementImpl(IControl element) + internal void ClearElementImpl(Control element) { // Clearing an element due to a collection change // is more strict in that pinned elements will be forcibly @@ -491,7 +491,7 @@ namespace Avalonia.Controls _viewportManager.OnElementCleared(element, GetVirtualizationInfo(element)); } - private int GetElementIndexImpl(IControl element) + private int GetElementIndexImpl(Control element) { // Verify that element is actually a child of this ItemsRepeater var parent = element.GetVisualParent(); @@ -505,9 +505,9 @@ namespace Avalonia.Controls return -1; } - private IControl? GetElementFromIndexImpl(int index) + private Control? GetElementFromIndexImpl(int index) { - IControl? result = null; + Control? result = null; var children = Children; for (var i = 0; i < children.Count && result == null; ++i) @@ -523,7 +523,7 @@ namespace Avalonia.Controls return result; } - private IControl GetOrCreateElementImpl(int index) + private Control GetOrCreateElementImpl(int index) { if (index >= 0 && index >= (ItemsSourceView?.Count ?? 0)) { @@ -545,7 +545,7 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot make an Anchor when there is no attached layout."); } - element = (IControl)GetLayoutContext().GetOrCreateElementAt(index); + element = (Control)GetLayoutContext().GetOrCreateElementAt(index); element.Measure(Size.Infinity); } @@ -555,7 +555,7 @@ namespace Avalonia.Controls return element!; } - internal void OnElementPrepared(IControl element, VirtualizationInfo virtInfo) + internal void OnElementPrepared(Control element, VirtualizationInfo virtInfo) { _viewportManager.OnElementPrepared(element, virtInfo); @@ -578,7 +578,7 @@ namespace Avalonia.Controls _childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element)); } - internal void OnElementClearing(IControl element) + internal void OnElementClearing(Control element) { if (ElementClearing != null) { @@ -597,7 +597,7 @@ namespace Avalonia.Controls _childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element)); } - internal void OnElementIndexChanged(IControl element, int oldIndex, int newIndex) + internal void OnElementIndexChanged(Control element, int oldIndex, int newIndex) { if (ElementIndexChanged != null) { diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementClearingEventArgs.cs b/src/Avalonia.Controls/Repeater/ItemsRepeaterElementClearingEventArgs.cs index 75d50e52a6..3968543bb6 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementClearingEventArgs.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeaterElementClearingEventArgs.cs @@ -12,13 +12,13 @@ namespace Avalonia.Controls /// public class ItemsRepeaterElementClearingEventArgs : EventArgs { - internal ItemsRepeaterElementClearingEventArgs(IControl element) => Element = element; + internal ItemsRepeaterElementClearingEventArgs(Control element) => Element = element; /// /// Gets the element that is being cleared for re-use. /// - public IControl Element { get; private set; } + public Control Element { get; private set; } - internal void Update(IControl element) => Element = element; + internal void Update(Control element) => Element = element; } } diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementIndexChangedEventArgs.cs b/src/Avalonia.Controls/Repeater/ItemsRepeaterElementIndexChangedEventArgs.cs index 9f1c32bf64..daa6aacaf4 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementIndexChangedEventArgs.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeaterElementIndexChangedEventArgs.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// public class ItemsRepeaterElementIndexChangedEventArgs : EventArgs { - internal ItemsRepeaterElementIndexChangedEventArgs(IControl element, int oldIndex, int newIndex) + internal ItemsRepeaterElementIndexChangedEventArgs(Control element, int oldIndex, int newIndex) { Element = element; OldIndex = oldIndex; @@ -22,7 +22,7 @@ namespace Avalonia.Controls /// /// Get the element for which the index changed. /// - public IControl Element { get; private set; } + public Control Element { get; private set; } /// /// Gets the index of the element after the change. @@ -34,7 +34,7 @@ namespace Avalonia.Controls /// public int OldIndex { get; private set; } - internal void Update(IControl element, int oldIndex, int newIndex) + internal void Update(Control element, int oldIndex, int newIndex) { Element = element; NewIndex = newIndex; diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementPreparedEventArgs.cs b/src/Avalonia.Controls/Repeater/ItemsRepeaterElementPreparedEventArgs.cs index 5a30dbcf2a..95ca53864d 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementPreparedEventArgs.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeaterElementPreparedEventArgs.cs @@ -10,7 +10,7 @@ namespace Avalonia.Controls /// public class ItemsRepeaterElementPreparedEventArgs { - internal ItemsRepeaterElementPreparedEventArgs(IControl element, int index) + internal ItemsRepeaterElementPreparedEventArgs(Control element, int index) { Element = element; Index = index; @@ -19,14 +19,14 @@ namespace Avalonia.Controls /// /// Gets the prepared element. /// - public IControl Element { get; private set; } + public Control Element { get; private set; } /// /// Gets the index of the item the element was prepared for. /// public int Index { get; private set; } - internal void Update(IControl element, int index) + internal void Update(Control element, int index) { Element = element; Index = index; diff --git a/src/Avalonia.Controls/Repeater/RecyclePool.cs b/src/Avalonia.Controls/Repeater/RecyclePool.cs index cf2b40836e..3e101dc631 100644 --- a/src/Avalonia.Controls/Repeater/RecyclePool.cs +++ b/src/Avalonia.Controls/Repeater/RecyclePool.cs @@ -30,7 +30,7 @@ namespace Avalonia.Controls public static void SetPoolInstance(IDataTemplate dataTemplate, RecyclePool value) => s_pools.Add(dataTemplate, value); - public void PutElement(IControl element, string key, IControl? owner) + public void PutElement(Control element, string key, Control? owner) { var ownerAsPanel = EnsureOwnerIsPanelOrNull(owner); var elementInfo = new ElementInfo(element, ownerAsPanel); @@ -44,7 +44,7 @@ namespace Avalonia.Controls pool.Add(elementInfo); } - public IControl? TryGetElement(string key, IControl? owner) + public Control? TryGetElement(string key, Control? owner) { if (_elements.TryGetValue(key, out var elements)) { @@ -80,12 +80,12 @@ namespace Avalonia.Controls return null; } - internal string GetReuseKey(IControl element) => ((Control)element).GetValue(ReuseKeyProperty); - internal void SetReuseKey(IControl element, string value) => ((Control)element).SetValue(ReuseKeyProperty, value); + internal string GetReuseKey(Control element) => element.GetValue(ReuseKeyProperty); + internal void SetReuseKey(Control element, string value) => element.SetValue(ReuseKeyProperty, value); - private IPanel? EnsureOwnerIsPanelOrNull(IControl? owner) + private Panel? EnsureOwnerIsPanelOrNull(Control? owner) { - if (owner is IPanel panel) + if (owner is Panel panel) { return panel; } @@ -99,14 +99,14 @@ namespace Avalonia.Controls private class ElementInfo { - public ElementInfo(IControl element, IPanel? owner) + public ElementInfo(Control element, Panel? owner) { Element = element; Owner = owner; } - - public IControl Element { get; } - public IPanel? Owner { get;} + + public Control Element { get; } + public Panel? Owner { get;} } } } diff --git a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs index c1baa66433..9132bef3d9 100644 --- a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs +++ b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls { public string? TemplateKey { get; set; } public object? DataContext { get; internal set; } - public IControl? Owner { get; internal set; } + public Control? Owner { get; internal set; } } public class RecyclingElementFactory : ElementFactory @@ -37,7 +37,7 @@ namespace Avalonia.Controls public event EventHandler? SelectTemplateKey; - protected override IControl GetElementCore(ElementFactoryGetArgs args) + protected override Control GetElementCore(ElementFactoryGetArgs args) { if (_templates == null || _templates.Count == 0) { @@ -85,9 +85,9 @@ namespace Avalonia.Controls RecyclePool.PutElement(element, key, args.Parent); } - protected virtual string OnSelectTemplateKeyCore(object? dataContext, IControl? owner) + protected virtual string OnSelectTemplateKeyCore(object? dataContext, Control? owner) { - if (SelectTemplateKey is object) + if (SelectTemplateKey is not null) { _args ??= new SelectTemplateEventArgs(); _args.TemplateKey = null; diff --git a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs b/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs index 3370b36801..49c8b9ff92 100644 --- a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs +++ b/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls protected override int ItemCountCore() => _owner.ItemsSourceView?.Count ?? 0; - protected override ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options) + protected override Layoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options) { return _owner.GetElementImpl( index, @@ -59,10 +59,10 @@ namespace Avalonia.Controls protected override object GetItemAtCore(int index) => _owner.ItemsSourceView!.GetAt(index)!; - protected override void RecycleElementCore(ILayoutable element) + protected override void RecycleElementCore(Layoutable element) { - Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "RepeaterLayout - RecycleElement: {Index}", _owner.GetElementIndex((IControl)element)); - _owner.ClearElementImpl((IControl)element); + Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "RepeaterLayout - RecycleElement: {Index}", _owner.GetElementIndex((Control)element)); + _owner.ClearElementImpl((Control)element); } protected override Rect RealizationRectCore() => _owner.RealizationWindow; diff --git a/src/Avalonia.Controls/Repeater/UniqueIdElementPool.cs b/src/Avalonia.Controls/Repeater/UniqueIdElementPool.cs index 778b8bebcc..5c7298d448 100644 --- a/src/Avalonia.Controls/Repeater/UniqueIdElementPool.cs +++ b/src/Avalonia.Controls/Repeater/UniqueIdElementPool.cs @@ -10,14 +10,14 @@ using System.Text; namespace Avalonia.Controls { - internal class UniqueIdElementPool : IEnumerable> + internal class UniqueIdElementPool : IEnumerable> { - private readonly Dictionary _elementMap = new Dictionary(); + private readonly Dictionary _elementMap = new Dictionary(); private readonly ItemsRepeater _owner; public UniqueIdElementPool(ItemsRepeater owner) => _owner = owner; - public void Add(IControl element) + public void Add(Control element) { var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); var key = virtInfo.UniqueId!; @@ -30,7 +30,7 @@ namespace Avalonia.Controls _elementMap.Add(key, element); } - public IControl? Remove(int index) + public Control? Remove(int index) { // Check if there is already a element in the mapping and if so, use it. string key = _owner.ItemsSourceView!.KeyFromIndex(index); @@ -48,7 +48,7 @@ namespace Avalonia.Controls _elementMap.Clear(); } - public IEnumerator> GetEnumerator() => _elementMap.GetEnumerator(); + public IEnumerator> GetEnumerator() => _elementMap.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index 12db48a2c2..2dff18cd04 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls private readonly ItemsRepeater _owner; private readonly List _pinnedPool = new List(); private readonly UniqueIdElementPool _resetPool; - private IControl? _lastFocusedElement; + private Control? _lastFocusedElement; private bool _isDataSourceStableResetPending; private ElementFactoryGetArgs? _elementFactoryGetArgs; private ElementFactoryRecycleArgs? _elementFactoryRecycleArgs; @@ -36,7 +36,7 @@ namespace Avalonia.Controls _resetPool = new UniqueIdElementPool(owner); } - public IControl GetElement(int index, bool forceCreate, bool suppressAutoRecycle) + public Control GetElement(int index, bool forceCreate, bool suppressAutoRecycle) { var element = forceCreate ? null : GetElementIfAlreadyHeldByLayout(index); if (element == null) @@ -73,7 +73,7 @@ namespace Avalonia.Controls return element; } - public void ClearElement(IControl element, bool isClearedDueToCollectionChange) + public void ClearElement(Control element, bool isClearedDueToCollectionChange) { var virtInfo = ItemsRepeater.GetVirtualizationInfo(element); var index = virtInfo.Index; @@ -119,7 +119,7 @@ namespace Avalonia.Controls // // In all of those three cases, we the ItemTemplateShim is NOT null. // Luckily when we create the items, we store whether we were the once setting the DataContext. - public void ClearElementToElementFactory(IControl element) + public void ClearElementToElementFactory(Control element) { _owner.OnElementClearing(element); @@ -179,14 +179,14 @@ namespace Avalonia.Controls } } - IControl? FindFocusCandidate(int clearedIndex, out IControl? focusedChild) + Control? FindFocusCandidate(int clearedIndex, out Control? focusedChild) { // Walk through all the children and find elements with index before and after the cleared index. // Note that during a delete the next element would now have the same index. int previousIndex = int.MinValue; int nextIndex = int.MaxValue; - IControl? nextElement = null; - IControl? previousElement = null; + Control? nextElement = null; + Control? previousElement = null; foreach (var child in _owner.Children) { @@ -254,16 +254,16 @@ namespace Avalonia.Controls } } - public void UpdatePin(IControl element, bool addPin) + public void UpdatePin(Control element, bool addPin) { var parent = element.VisualParent; - var child = (IVisual)element; + var child = (Visual)element; while (parent != null) { if (parent is ItemsRepeater repeater) { - var virtInfo = ItemsRepeater.GetVirtualizationInfo((IControl)child); + var virtInfo = ItemsRepeater.GetVirtualizationInfo((Control)child); if (virtInfo.IsRealized) { if (addPin) @@ -482,9 +482,9 @@ namespace Avalonia.Controls // If an index that is realized is requested by the layout, we unfortunately have to walk the // children. Not ideal, but a reasonable default to provide consistent behavior between virtualizing // and non-virtualizing hosts. - private IControl? GetElementIfAlreadyHeldByLayout(int index) + private Control? GetElementIfAlreadyHeldByLayout(int index) { - IControl? element = null; + Control? element = null; bool cachedFirstLastIndicesInvalid = _firstRealizedElementIndexHeldByLayout == FirstRealizedElementIndexDefault; bool isRequestedIndexInRealizedRange = (_firstRealizedElementIndexHeldByLayout <= index && index <= _lastRealizedElementIndexHeldByLayout); @@ -518,9 +518,9 @@ namespace Avalonia.Controls return element; } - private IControl? GetElementFromUniqueIdResetPool(int index) + private Control? GetElementFromUniqueIdResetPool(int index) { - IControl? element = null; + Control? element = null; // See if you can get it from the reset pool. if (_isDataSourceStableResetPending) { @@ -541,9 +541,9 @@ namespace Avalonia.Controls return element; } - private IControl? GetElementFromPinnedElements(int index) + private Control? GetElementFromPinnedElements(int index) { - IControl? element = null; + Control? element = null; // See if you can find something among the pinned elements. for (var i = 0; i < _pinnedPool.Count; ++i) @@ -570,17 +570,17 @@ namespace Avalonia.Controls // There are several cases handled here with respect to which element gets returned and when DataContext is modified. // // 1. If there is no ItemTemplate: - // 1.1 If data is an IControl -> the data is returned - // 1.2 If data is not an IControl -> a default DataTemplate is used to fetch element and DataContext is set to data + // 1.1 If data is an Control -> the data is returned + // 1.2 If data is not an Control -> a default DataTemplate is used to fetch element and DataContext is set to data // // 2. If there is an ItemTemplate: - // 2.1 If data is not an IControl -> Element is fetched from ElementFactory and DataContext is set to the data - // 2.2 If data is an IControl: + // 2.1 If data is not an Control -> Element is fetched from ElementFactory and DataContext is set to the data + // 2.2 If data is an Control: // 2.2.1 If Element returned by the ElementFactory is the same as the data -> Element (a.k.a. data) is returned as is // 2.2.2 If Element returned by the ElementFactory is not the same as the data // -> Element that is fetched from the ElementFactory is returned and // DataContext is set to the data's DataContext (if it exists), otherwise it is set to the data itself - private IControl GetElementFromElementFactory(int index) + private Control GetElementFromElementFactory(int index) { // The view generator is the provider of last resort. var data = _owner.ItemsSourceView!.GetAt(index); @@ -598,11 +598,11 @@ namespace Avalonia.Controls return providedElementFactory; } - IControl GetElement() + Control GetElement() { if (providedElementFactory == null) { - if (data is IControl dataAsElement) + if (data is Control dataAsElement) { return dataAsElement; } @@ -670,7 +670,7 @@ namespace Avalonia.Controls return element; } - private bool ClearElementToUniqueIdResetPool(IControl element, VirtualizationInfo virtInfo) + private bool ClearElementToUniqueIdResetPool(Control element, VirtualizationInfo virtInfo) { if (_isDataSourceStableResetPending) { @@ -681,7 +681,7 @@ namespace Avalonia.Controls return _isDataSourceStableResetPending; } - private bool ClearElementToPinnedPool(IControl element, VirtualizationInfo virtInfo, bool isClearedDueToCollectionChange) + private bool ClearElementToPinnedPool(Control element, VirtualizationInfo virtInfo, bool isClearedDueToCollectionChange) { bool moveToPinnedPool = !isClearedDueToCollectionChange && virtInfo.IsPinned; @@ -697,11 +697,9 @@ namespace Avalonia.Controls private void UpdateFocusedElement() { - IControl? focusedElement = null; + Control? focusedElement = null; - var child = FocusManager.Instance?.Current; - - if (child != null) + if (FocusManager.Instance?.Current is Visual child) { var parent = child.VisualParent; var owner = _owner; @@ -712,7 +710,7 @@ namespace Avalonia.Controls { if (parent is ItemsRepeater repeater) { - var element = child as IControl; + var element = child as Control; if (repeater == owner && element is not null && ItemsRepeater.GetVirtualizationInfo(element).IsRealized) @@ -723,7 +721,7 @@ namespace Avalonia.Controls break; } - child = parent as IInputElement; + child = parent; parent = child?.VisualParent; } } @@ -758,7 +756,7 @@ namespace Avalonia.Controls } } - private void UpdateElementIndex(IControl element, VirtualizationInfo virtInfo, int index) + private void UpdateElementIndex(Control element, VirtualizationInfo virtInfo, int index) { var oldIndex = virtInfo.Index; if (oldIndex != index) @@ -776,13 +774,13 @@ namespace Avalonia.Controls private struct PinnedElementInfo { - public PinnedElementInfo(IControl element) + public PinnedElementInfo(Control element) { PinnedElement = element; VirtualizationInfo = ItemsRepeater.GetVirtualizationInfo(element); } - public IControl PinnedElement { get; } + public Control PinnedElement { get; } public VirtualizationInfo VirtualizationInfo { get; } } } diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index 7f03cc575e..e24ed37f1e 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -25,7 +25,7 @@ namespace Avalonia.Controls private readonly ItemsRepeater _owner; private bool _ensuredScroller; private IScrollAnchorProvider? _scroller; - private IControl? _makeAnchorElement; + private Control? _makeAnchorElement; private bool _isAnchorOutsideRealizedRange; private Rect _visibleWindow; private Rect _layoutExtent; @@ -57,7 +57,7 @@ namespace Avalonia.Controls _owner = owner; } - public IControl? SuggestedAnchor + public Control? SuggestedAnchor { get { @@ -75,7 +75,7 @@ namespace Avalonia.Controls // be a direct child of ours, or even an indirect child. We need to walk up the tree starting // from anchorElement to figure out what child of ours (if any) to use as the suggested element. var child = anchorElement; - var parent = child.VisualParent as IControl; + var parent = child.VisualParent as Control; while (parent != null) { @@ -86,7 +86,7 @@ namespace Avalonia.Controls } child = parent; - parent = parent.VisualParent as IControl; + parent = parent.VisualParent as Control; } } } @@ -97,7 +97,7 @@ namespace Avalonia.Controls public bool HasScroller => _scroller != null; - public IControl? MadeAnchor => _makeAnchorElement; + public Control? MadeAnchor => _makeAnchorElement; public double HorizontalCacheLength { @@ -213,7 +213,7 @@ namespace Avalonia.Controls // We just finished a measure pass and have a new extent. // Let's make sure the scrollers will run its arrange so that they track the anchor. - ((IControl?)_scroller)?.InvalidateArrange(); + ((Control?)_scroller)?.InvalidateArrange(); } public Point GetOrigin() => _layoutExtent.TopLeft; @@ -239,7 +239,7 @@ namespace Avalonia.Controls } } - public void OnElementPrepared(IControl element, VirtualizationInfo virtInfo) + public void OnElementPrepared(Control element, VirtualizationInfo virtInfo) { // WinUI registers the element as an anchor candidate here, but I feel that's in error: // at this point the element has not yet been positioned by the arrange pass so it will @@ -249,7 +249,7 @@ namespace Avalonia.Controls virtInfo.IsRegisteredAsAnchorCandidate = false; } - public void OnElementCleared(IControl element, VirtualizationInfo virtInfo) + public void OnElementCleared(Control element, VirtualizationInfo virtInfo) { _scroller?.UnregisterAnchorCandidate(element); virtInfo.IsRegisteredAsAnchorCandidate = false; @@ -325,7 +325,7 @@ namespace Avalonia.Controls } } - public void OnMakeAnchor(IControl? anchor, bool isAnchorOutsideRealizedRange) + public void OnMakeAnchor(Control? anchor, bool isAnchorOutsideRealizedRange) { if (_makeAnchorElement != anchor) { @@ -347,7 +347,7 @@ namespace Avalonia.Controls // get the targetChild - i.e the immediate child of this repeater that is being brought into view. // Note that the element being brought into view could be a descendant. - var targetChild = GetImmediateChildOfRepeater((IControl)args.TargetObject!); + var targetChild = GetImmediateChildOfRepeater((Control)args.TargetObject!); if (targetChild is null) { @@ -381,7 +381,7 @@ namespace Avalonia.Controls } } - public void RegisterScrollAnchorCandidate(IControl element, VirtualizationInfo virtInfo) + public void RegisterScrollAnchorCandidate(Control element, VirtualizationInfo virtInfo) { if (!virtInfo.IsRegisteredAsAnchorCandidate) { @@ -390,14 +390,14 @@ namespace Avalonia.Controls } } - private IControl? GetImmediateChildOfRepeater(IControl descendant) + private Control? GetImmediateChildOfRepeater(Control descendant) { var targetChild = descendant; - var parent = (IControl?)descendant.VisualParent; + var parent = (Control?)descendant.VisualParent; while (parent != null && parent != _owner) { targetChild = parent; - parent = (IControl?)parent.VisualParent; + parent = (Control?)parent.VisualParent; } if (parent == null) diff --git a/src/Avalonia.Controls/RequestBringIntoViewEventArgs.cs b/src/Avalonia.Controls/RequestBringIntoViewEventArgs.cs index 5396ad67a7..a9c84cd4ec 100644 --- a/src/Avalonia.Controls/RequestBringIntoViewEventArgs.cs +++ b/src/Avalonia.Controls/RequestBringIntoViewEventArgs.cs @@ -5,7 +5,7 @@ namespace Avalonia.Controls { public class RequestBringIntoViewEventArgs : RoutedEventArgs { - public IVisual? TargetObject { get; set; } + public Visual? TargetObject { get; set; } public Rect TargetRect { get; set; } } diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index dde6b71e6e..22f9c0832a 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls return _iScreenImpl.ScreenFromPoint(point); } - public Screen? ScreenFromVisual(IVisual visual) + public Screen? ScreenFromVisual(Visual visual) { var tl = visual.PointToScreen(visual.Bounds.TopLeft); var br = visual.PointToScreen(visual.Bounds.BottomRight); diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 535f9ae43e..12fb9ba5c5 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -344,7 +344,7 @@ namespace Avalonia.Controls } /// - public IControl? CurrentAnchor => (Presenter as IScrollAnchorProvider)?.CurrentAnchor; + public Control? CurrentAnchor => (Presenter as IScrollAnchorProvider)?.CurrentAnchor; /// /// Gets the maximum horizontal scrollbar value. @@ -615,13 +615,13 @@ namespace Avalonia.Controls } /// - public void RegisterAnchorCandidate(IControl element) + public void RegisterAnchorCandidate(Control element) { (Presenter as IScrollAnchorProvider)?.RegisterAnchorCandidate(element); } /// - public void UnregisterAnchorCandidate(IControl element) + public void UnregisterAnchorCandidate(Control element) { (Presenter as IScrollAnchorProvider)?.UnregisterAnchorCandidate(element); } @@ -633,7 +633,7 @@ namespace Avalonia.Controls if (base.RegisterContentPresenter(presenter)) { - _childSubscription = Presenter? + _childSubscription = ((Control?)Presenter)? .GetObservable(ContentPresenter.ChildProperty) .Subscribe(ChildChanged); return true; @@ -660,7 +660,7 @@ namespace Avalonia.Controls return double.IsNaN(result) ? 0 : result; } - private void ChildChanged(IControl? child) + private void ChildChanged(Control? child) { if (_logicalScrollable is object) { diff --git a/src/Avalonia.Controls/Selection/IndexRange.cs b/src/Avalonia.Controls/Selection/IndexRange.cs index dd3445fb70..3b7830002b 100644 --- a/src/Avalonia.Controls/Selection/IndexRange.cs +++ b/src/Avalonia.Controls/Selection/IndexRange.cs @@ -86,7 +86,7 @@ namespace Avalonia.Controls.Selection return hashCode; } - public override string ToString() => $"[{Begin}..{End}]"; + public override string ToString() => FormattableString.Invariant($"[{Begin}..{End}]"); public static bool operator ==(IndexRange left, IndexRange right) => left.Equals(right); public static bool operator !=(IndexRange left, IndexRange right) => !(left == right); diff --git a/src/Avalonia.Controls/Selection/SelectionNodeBase.cs b/src/Avalonia.Controls/Selection/SelectionNodeBase.cs index 5994025d56..33a5525acc 100644 --- a/src/Avalonia.Controls/Selection/SelectionNodeBase.cs +++ b/src/Avalonia.Controls/Selection/SelectionNodeBase.cs @@ -290,12 +290,12 @@ namespace Avalonia.Controls.Selection // so bail. // // See unit test Handles_Selection_Made_In_CollectionChanged for more details. - if (ItemsView is object && + if (ItemsView is not null && RangesEnabled && Ranges.Count > 0 && e.Action == NotifyCollectionChangedAction.Add) { - var lastIndex = Ranges.Last().End; + var lastIndex = Ranges[Ranges.Count - 1].End; if (e.NewStartingIndex <= lastIndex) { diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs index 2dc642b163..85ea3b8472 100644 --- a/src/Avalonia.Controls/SizeChangedEventArgs.cs +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls /// /// The routed event associated with these event args. /// The source object that raised the routed event. - public SizeChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source) + public SizeChangedEventArgs(RoutedEvent? routedEvent, object? source) : base(routedEvent, source) { } @@ -37,7 +37,7 @@ namespace Avalonia.Controls /// The new size (or bounds) of the object. public SizeChangedEventArgs( RoutedEvent? routedEvent, - IInteractive? source, + object? source, Size previousSize, Size newSize) : base(routedEvent, source) diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index be87705b54..4b23717209 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -190,7 +190,7 @@ namespace Avalonia.Controls _increaseButtonSubscription?.Dispose(); _increaseButtonReleaseDispose?.Dispose(); _pointerMovedDispose?.Dispose(); - + _decreaseButton = e.NameScope.Find /// The container. /// Whether the control is selected - private void MarkContainerSelected(IControl container, bool selected) + private void MarkContainerSelected(Control container, bool selected) { if (container == null) { diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 2f96e6911f..9bfcf5adfa 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls /// /// An item in a . /// - [TemplatePart("PART_Header", typeof(IControl))] + [TemplatePart("PART_Header", typeof(Control))] [PseudoClasses(":pressed", ":selected")] public class TreeViewItem : HeaderedItemsControl, ISelectable { @@ -39,11 +39,11 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect( nameof(Level), o => o.Level); - private static readonly ITemplate DefaultPanel = - new FuncTemplate(() => new StackPanel()); + private static readonly ITemplate DefaultPanel = + new FuncTemplate(() => new StackPanel()); private TreeView? _treeView; - private IControl? _header; + private Control? _header; private bool _isExpanded; private int _level; private bool _templateApplied; @@ -198,7 +198,7 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - _header = e.NameScope.Find("PART_Header"); + _header = e.NameScope.Find("PART_Header"); _templateApplied = true; if (_deferredBringIntoViewFlag) { diff --git a/src/Avalonia.Controls/UserControl.cs b/src/Avalonia.Controls/UserControl.cs index 7b9cc2da1c..e9339d5f4b 100644 --- a/src/Avalonia.Controls/UserControl.cs +++ b/src/Avalonia.Controls/UserControl.cs @@ -1,4 +1,3 @@ -using System; using Avalonia.Styling; namespace Avalonia.Controls diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs index b15e072052..d2e3feb55a 100644 --- a/src/Avalonia.Controls/Utils/AncestorFinder.cs +++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs @@ -10,15 +10,15 @@ namespace Avalonia.Controls.Utils { class FinderNode : IDisposable { - private readonly IStyledElement _control; + private readonly StyledElement _control; private readonly Type _ancestorType; - public IObservable Observable => _subject; - private readonly Subject _subject = new Subject(); + public IObservable Observable => _subject; + private readonly Subject _subject = new Subject(); private FinderNode? _child; private IDisposable? _disposable; - public FinderNode(IStyledElement control, Type ancestorType) + public FinderNode(StyledElement control, Type ancestorType) { _control = control; _ancestorType = ancestorType; @@ -29,7 +29,7 @@ namespace Avalonia.Controls.Utils _disposable = _control.GetObservable(Control.ParentProperty).Subscribe(OnValueChanged); } - private void OnValueChanged(IStyledElement? next) + private void OnValueChanged(StyledElement? next) { if (next == null || _ancestorType.IsAssignableFrom(next.GetType())) _subject.OnNext(next); @@ -42,7 +42,7 @@ namespace Avalonia.Controls.Utils } } - private void OnChildValueChanged(IStyledElement? control) => _subject.OnNext(control); + private void OnChildValueChanged(StyledElement? control) => _subject.OnNext(control); public void Dispose() @@ -53,15 +53,15 @@ namespace Avalonia.Controls.Utils } } - public static IObservable Create(IStyledElement control) - where T : IStyledElement + public static IObservable Create(StyledElement control) + where T : StyledElement { return Create(control, typeof(T)).Select(x => (T?)x); } - public static IObservable Create(IStyledElement control, Type ancestorType) + public static IObservable Create(StyledElement control, Type ancestorType) { - return new AnonymousObservable(observer => + return new AnonymousObservable(observer => { var finder = new FinderNode(control, ancestorType); var subscription = finder.Observable.Subscribe(observer); diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 07d877142e..1c2ee7ec2c 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -26,7 +26,7 @@ namespace Avalonia.Controls /// /// Defines the property /// - public static readonly StyledProperty ChildProperty = + public static readonly StyledProperty ChildProperty = Decorator.ChildProperty.AddOwner(); static Viewbox() @@ -69,7 +69,7 @@ namespace Avalonia.Controls /// Gets or sets the child of the Viewbox /// [Content] - public IControl? Child + public Control? Child { get => GetValue(ChildProperty); set => SetValue(ChildProperty, value); @@ -91,7 +91,7 @@ namespace Avalonia.Controls if (change.Property == ChildProperty) { - var (oldChild, newChild) = change.GetOldAndNewValue(); + var (oldChild, newChild) = change.GetOldAndNewValue(); if (oldChild is not null) { @@ -153,9 +153,9 @@ namespace Avalonia.Controls /// private class ViewboxContainer : Control { - private IControl? _child; + private Control? _child; - public IControl? Child + public Control? Child { get => _child; set diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 93fbf6d17b..f7cf1b922d 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -80,7 +80,7 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - if (_forceRemeasure || availableSize != ((ILayoutable)this).PreviousMeasure) + if (_forceRemeasure || availableSize != PreviousMeasure) { _forceRemeasure = false; _availableSpace = availableSize; @@ -110,7 +110,7 @@ namespace Avalonia.Controls switch (e.Action) { case NotifyCollectionChangedAction.Add: - foreach (IControl control in e.NewItems!) + foreach (Control control in e.NewItems!) { UpdateAdd(control); } @@ -118,7 +118,7 @@ namespace Avalonia.Controls break; case NotifyCollectionChangedAction.Remove: - foreach (IControl control in e.OldItems!) + foreach (Control control in e.OldItems!) { UpdateRemove(control); } @@ -127,7 +127,7 @@ namespace Avalonia.Controls } } - protected override IInputElement? GetControlInDirection(NavigationDirection direction, IControl? from) + protected override IInputElement? GetControlInDirection(NavigationDirection direction, Control? from) { var logicalScrollable = Parent as ILogicalScrollable; @@ -142,7 +142,7 @@ namespace Avalonia.Controls } internal override void ArrangeChild( - IControl child, + Control child, Rect rect, Size panelSize, Orientation orientation) @@ -191,7 +191,7 @@ namespace Avalonia.Controls } } - private void UpdateAdd(IControl child) + private void UpdateAdd(Control child) { var bounds = Bounds; var spacing = Spacing; @@ -213,7 +213,7 @@ namespace Avalonia.Controls } } - private void UpdateRemove(IControl child) + private void UpdateRemove(Control child) { var bounds = Bounds; var spacing = Spacing; diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index b7061824f0..0426291d67 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -84,7 +84,7 @@ namespace Avalonia.Controls var orientation = Orientation; var children = Children; bool horiz = orientation == Orientation.Horizontal; - int index = from is not null ? Children.IndexOf((IControl)from) : -1; + int index = from is not null ? Children.IndexOf((Control)from) : -1; switch (direction) { diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 0be695e0a1..867c94b126 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -7,7 +7,7 @@ need change when there are breaking changes to designer support api. --> 0.7.0 - CS1591 + $(NoWarn);CS1591 true Avalonia diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 9a901f909a..b4cfffcdca 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -38,10 +38,9 @@ namespace Avalonia.DesignerSupport var useCompiledBindings = localAsm?.GetCustomAttributes() .FirstOrDefault(a => a.Key == "AvaloniaUseCompiledBindingsByDefault")?.Value; - var loaded = loader.Load(stream, new RuntimeXamlLoaderConfiguration + var loaded = loader.Load(new RuntimeXamlLoaderDocument(baseUri, stream), new RuntimeXamlLoaderConfiguration { LocalAssembly = localAsm, - BaseUri = baseUri, DesignMode = true, UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue ) && parsedValue }); diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index f100be5d5b..143980dfa5 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -162,7 +162,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport if (sendNow != null && socket != null) { await socket.SendMessage( - $"frame:{sendNow.SequenceId}:{sendNow.Width}:{sendNow.Height}:{sendNow.Stride}:{sendNow.DpiX}:{sendNow.DpiY}"); + FormattableString.Invariant($"frame:{sendNow.SequenceId}:{sendNow.Width}:{sendNow.Height}:{sendNow.Stride}:{sendNow.DpiX}:{sendNow.DpiY}")); await socket.SendMessage(false, sendNow.Data); } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs index 6bfae536c9..2d02262aa7 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs @@ -146,7 +146,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport public async Task RespondAsync(int code, byte[] data, string contentType) { - var headers = Encoding.UTF8.GetBytes($"HTTP/1.1 {code} {(HttpStatusCode)code}\r\nConnection: close\r\nContent-Type: {contentType}\r\nContent-Length: {data.Length}\r\n\r\n"); + var headers = Encoding.UTF8.GetBytes(FormattableString.Invariant($"HTTP/1.1 {code} {(HttpStatusCode)code}\r\nConnection: close\r\nContent-Type: {contentType}\r\nContent-Length: {data.Length}\r\n\r\n")); await _stream.WriteAsync(headers, 0, headers.Length); await _stream.WriteAsync(data, 0, data.Length); _stream.Dispose(); diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index ca2c2c7fb2..543d07f958 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Reflection; using System.Threading; @@ -150,10 +151,20 @@ namespace Avalonia.DesignerSupport.Remote } if (args.Method == Methods.Win32) - builder.UseWindowingSubsystem("Avalonia.Win32"); + builder.UseWindowingSubsystem(GetInitializer("Avalonia.Win32"), "Win32"); builder.SetupWithoutStarting(); return transport; } + + private static Action GetInitializer(string assemblyName) => () => + { + var assembly = Assembly.Load(new AssemblyName(assemblyName)); + var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform"; + var platformClassFullName = assemblyName + "." + platformClassName; + var platformClass = assembly.GetType(platformClassFullName); + var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes); + init!.Invoke(null, null); + }; } private const string BuilderMethodName = "BuildAvaloniaApp"; diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 7d4a6518ef..71389be1a6 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -61,7 +61,7 @@ namespace Avalonia.DesignerSupport.Remote })); } - public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root); public void Dispose() { } diff --git a/src/Avalonia.Desktop/Avalonia.Desktop.csproj b/src/Avalonia.Desktop/Avalonia.Desktop.csproj index d423aa7dae..d180e6e34d 100644 --- a/src/Avalonia.Desktop/Avalonia.Desktop.csproj +++ b/src/Avalonia.Desktop/Avalonia.Desktop.csproj @@ -12,5 +12,7 @@ - + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 2ca8549cfa..42c5ea1fa9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -10,7 +10,7 @@ namespace Avalonia.Diagnostics.Controls { private readonly App _application; - private static readonly Version s_version = typeof(IAvaloniaObject).Assembly?.GetName()?.Version + private static readonly Version s_version = typeof(AvaloniaObject).Assembly?.GetName()?.Version ?? Version.Parse("0.0.00"); public event EventHandler? Closed; @@ -31,7 +31,7 @@ namespace Avalonia.Diagnostics.Controls RendererRoot = application.ApplicationLifetime switch { Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow?.Renderer, - Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as VisualTree.IVisual)?.VisualRoot?.Renderer, + Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as Visual)?.VisualRoot?.Renderer, _ => null }; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 9029ddf2bd..8eadcee6f9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -105,7 +105,7 @@ namespace Avalonia.Diagnostics private static IDisposable Open(Application? application, DevToolsOptions options, Window? owner = default) { - var focussedControl = KeyboardDevice.Instance?.FocusedElement as IControl; + var focussedControl = KeyboardDevice.Instance?.FocusedElement as Control; if (application is null) { throw new ArgumentNullException(nameof(application)); diff --git a/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs index 3503ab56b3..d1b3e08352 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs @@ -12,6 +12,6 @@ namespace Avalonia.Diagnostics /// Handle the Screenshot /// /// - Task Take(IControl control); + Task Take(Control control); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs index e80f176258..acf2e01264 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs @@ -13,9 +13,9 @@ namespace Avalonia.Diagnostics.Screenshots /// /// /// stream to render the control - protected abstract Task GetStream(IControl control); + protected abstract Task GetStream(Control control); - public async Task Take(IControl control) + public async Task Take(Control control) { using var output = await GetStream(control); if (output is { }) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs index 501746ffb0..2117996b96 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs @@ -47,7 +47,7 @@ namespace Avalonia.Diagnostics.Screenshots /// public string Title { get; } = "Save Screenshot to ..."; - Window GetWindow(IControl control) + static Window GetWindow(Control control) { var window = control.VisualRoot as Window; var app = Application.Current; @@ -58,7 +58,7 @@ namespace Avalonia.Diagnostics.Screenshots return window!; } - protected override async Task GetStream(IControl control) + protected override async Task GetStream(Control control) { var result = await GetWindow(control).StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs index 23fc5e3d93..6ddfb138f3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs @@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics { internal class ViewLocator : IDataTemplate { - public IControl? Build(object? data) + public Control? Build(object? data) { if (data is null) return null; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 71da543d70..5cf9e17ecf 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -16,7 +16,7 @@ namespace Avalonia.Diagnostics.ViewModels { internal class ControlDetailsViewModel : ViewModelBase, IDisposable, IClassesChangedListener { - private readonly IAvaloniaObject _avaloniaObject; + private readonly AvaloniaObject _avaloniaObject; private IDictionary? _propertyIndex; private PropertyViewModel? _selectedProperty; private DataGridCollectionView? _propertiesView; @@ -29,16 +29,16 @@ namespace Avalonia.Diagnostics.ViewModels private string? _selectedEntityType; private bool _showImplementedInterfaces; - public ControlDetailsViewModel(TreePageViewModel treePage, IAvaloniaObject avaloniaObject) + public ControlDetailsViewModel(TreePageViewModel treePage, AvaloniaObject avaloniaObject) { _avaloniaObject = avaloniaObject; TreePage = treePage; - Layout = avaloniaObject is IVisual - ? new ControlLayoutViewModel((IVisual)avaloniaObject) + Layout = avaloniaObject is Visual + ? new ControlLayoutViewModel((Visual)avaloniaObject) : default; - NavigateToProperty(_avaloniaObject, (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString()); + NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString()); AppliedStyles = new ObservableCollection(); PseudoClasses = new ObservableCollection(); @@ -82,7 +82,7 @@ namespace Avalonia.Diagnostics.ViewModels { var setterValue = regularSetter.Value; - var resourceInfo = GetResourceInfo(setterValue); + var resourceInfo = GetResourceInfo(setterValue); SetterViewModel setterVm; @@ -121,7 +121,7 @@ namespace Avalonia.Diagnostics.ViewModels public bool CanNavigateToParentProperty => _selectedEntitiesStack.Count >= 1; - private (object resourceKey, bool isDynamic)? GetResourceInfo(object? value) + private static (object resourceKey, bool isDynamic)? GetResourceInfo(object? value) { if (value is StaticResourceExtension staticResource) { @@ -136,7 +136,7 @@ namespace Avalonia.Diagnostics.ViewModels return null; } - private bool IsBinding(object? value) + private static bool IsBinding(object? value) { switch (value) { @@ -253,7 +253,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private IEnumerable GetAvaloniaProperties(object o) + private static IEnumerable GetAvaloniaProperties(object o) { if (o is AvaloniaObject ao) { @@ -267,7 +267,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private IEnumerable GetClrProperties(object o, bool showImplementedInterfaces) + private static IEnumerable GetClrProperties(object o, bool showImplementedInterfaces) { foreach (var p in GetClrProperties(o, o.GetType())) { @@ -286,7 +286,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private IEnumerable GetClrProperties(object o, Type t) + private static IEnumerable GetClrProperties(object o, Type t) { return t.GetProperties() .Where(x => x.GetIndexParameters().Length == 0) @@ -411,7 +411,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private int GroupIndex(string? group) + private static int GroupIndex(string? group) { switch (group) { @@ -447,7 +447,7 @@ namespace Avalonia.Diagnostics.ViewModels { case AvaloniaPropertyViewModel avaloniaProperty: - property = (_selectedEntity as IControl)?.GetValue(avaloniaProperty.Property); + property = (_selectedEntity as Control)?.GetValue(avaloniaProperty.Property); break; @@ -496,7 +496,7 @@ namespace Avalonia.Diagnostics.ViewModels switch (oldSelectedEntity) { - case IAvaloniaObject ao1: + case AvaloniaObject ao1: ao1.PropertyChanged -= ControlPropertyChanged; break; @@ -528,7 +528,7 @@ namespace Avalonia.Diagnostics.ViewModels switch (o) { - case IAvaloniaObject ao2: + case AvaloniaObject ao2: ao2.PropertyChanged += ControlPropertyChanged; break; @@ -546,7 +546,7 @@ namespace Avalonia.Diagnostics.ViewModels { NavigateToProperty( _avaloniaObject, - (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString()); + (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString()); } if (PropertiesView is null) @@ -569,7 +569,7 @@ namespace Avalonia.Diagnostics.ViewModels { _showImplementedInterfaces = showImplementedInterfaces; SelectedProperty = null; - NavigateToProperty(_avaloniaObject, (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString()); + NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString()); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs index a453fef212..6b56806250 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -9,7 +9,7 @@ namespace Avalonia.Diagnostics.ViewModels { internal class ControlLayoutViewModel : ViewModelBase { - private readonly IVisual _control; + private readonly Visual _control; private Thickness _borderThickness; private double _height; private string? _heightConstraint; @@ -21,7 +21,7 @@ namespace Avalonia.Diagnostics.ViewModels private double _width; private string? _widthConstraint; - public ControlLayoutViewModel(IVisual control) + public ControlLayoutViewModel(Visual control) { _control = control; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index e8de22710a..7da43a51b7 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -65,7 +65,7 @@ namespace Avalonia.Diagnostics.ViewModels { if (!_isRegistered || IsEnabled == false) return; - if (sender is IVisual v && BelongsToDevTool(v)) + if (sender is Visual v && BelongsToDevTool(v)) return; var s = sender!; @@ -99,7 +99,7 @@ namespace Avalonia.Diagnostics.ViewModels { if (!_isRegistered || IsEnabled == false) return; - if (e.Source is IVisual v && BelongsToDevTool(v)) + if (e.Source is Visual v && BelongsToDevTool(v)) return; var s = e.Source; @@ -124,7 +124,7 @@ namespace Avalonia.Diagnostics.ViewModels handler(); } - private static bool BelongsToDevTool(IVisual v) + private static bool BelongsToDevTool(Visual v) { var current = v; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs index fbcedb2e74..c5868e0e0c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs @@ -78,7 +78,7 @@ namespace Avalonia.Diagnostics.ViewModels public void RequestTreeNavigateTo(EventChainLink navTarget) { - if (navTarget.Handler is IControl control) + if (navTarget.Handler is Control control) { _mainViewModel.RequestTreeNavigateTo(control, true); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index e8a5076c36..e71f0bcaec 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -10,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels { internal class LogicalTreeNode : TreeNode { - public LogicalTreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent) + public LogicalTreeNode(AvaloniaObject avaloniaObject, TreeNode? parent) : base(avaloniaObject, parent) { Children = avaloniaObject switch @@ -25,7 +25,7 @@ namespace Avalonia.Diagnostics.ViewModels public static LogicalTreeNode[] Create(object control) { - var logical = control as IAvaloniaObject; + var logical = control as AvaloniaObject; return logical != null ? new[] { new LogicalTreeNode(logical, null) } : Array.Empty(); } @@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels protected override void Initialize(AvaloniaList nodes) { _subscription = _control.LogicalChildren.ForEachItem( - (i, item) => nodes.Insert(i, new LogicalTreeNode((IAvaloniaObject)item, Owner)), + (i, item) => nodes.Insert(i, new LogicalTreeNode((AvaloniaObject)item, Owner)), (i, item) => nodes.RemoveAt(i), () => nodes.Clear()); } @@ -92,7 +92,7 @@ namespace Avalonia.Diagnostics.ViewModels { return; } - nodes.Add(new LogicalTreeNode((IAvaloniaObject)s!,Owner)); + nodes.Add(new LogicalTreeNode((AvaloniaObject)s!,Owner)); }), Window.WindowClosedEvent.AddClassHandler(typeof(Window), (s,e)=> { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 6b21b3f72b..52535aa991 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -147,7 +147,7 @@ namespace Avalonia.Diagnostics.ViewModels { if (_content is TreePageViewModel oldTree && value is TreePageViewModel newTree && - oldTree?.SelectedNode?.Visual is IControl control) + oldTree?.SelectedNode?.Visual is Control control) { // HACK: We want to select the currently selected control in the new tree, but // to select nested nodes in TreeView, currently the TreeView has to be able to @@ -232,7 +232,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - public void SelectControl(IControl control) + public void SelectControl(Control control) { var tree = Content as TreePageViewModel; @@ -274,7 +274,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - public void RequestTreeNavigateTo(IControl control, bool isVisualTree) + public void RequestTreeNavigateTo(Control control, bool isVisualTree) { var tree = isVisualTree ? _visualTree : _logicalTree; @@ -296,13 +296,13 @@ namespace Avalonia.Diagnostics.ViewModels { return Content is TreePageViewModel tree && tree.SelectedNode != null - && tree.SelectedNode.Visual is VisualTree.IVisual visual + && tree.SelectedNode.Visual is Visual visual && visual.VisualRoot != null; } public async void Shot(object? parameter) { - if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control + if ((Content as TreePageViewModel)?.SelectedNode?.Visual is Control control && _screenshotHandler is { } ) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 4fc55333d0..65bfd7fc36 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.ViewModels private string _classes; private bool _isExpanded; - protected TreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null) + protected TreeNode(AvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null) { _classes = string.Empty; Parent = parent; @@ -24,7 +24,7 @@ namespace Avalonia.Diagnostics.ViewModels Visual = visual!; FontWeight = IsRoot ? FontWeight.Bold : FontWeight.Normal; - if (visual is IControl control) + if (visual is Control control) { ElementName = control.Name; @@ -76,7 +76,7 @@ namespace Avalonia.Diagnostics.ViewModels get; } - public IAvaloniaObject Visual + public AvaloniaObject Visual { get; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index ff3361dc41..e916995bae 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -69,7 +69,7 @@ namespace Avalonia.Diagnostics.ViewModels _details?.Dispose(); } - public TreeNode? FindNode(IControl control) + public TreeNode? FindNode(Control control) { foreach (var node in Nodes) { @@ -84,10 +84,10 @@ namespace Avalonia.Diagnostics.ViewModels return null; } - public void SelectControl(IControl control) + public void SelectControl(Control control) { var node = default(TreeNode); - IControl? c = control; + Control? c = control; while (node == null && c != null) { @@ -95,7 +95,7 @@ namespace Avalonia.Diagnostics.ViewModels if (node == null) { - c = c.GetVisualParent(); + c = c.GetVisualParent(); } } @@ -115,7 +115,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private TreeNode? FindNode(TreeNode node, IControl control) + private TreeNode? FindNode(TreeNode node, Control control) { if (node.Visual == control) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index 4a5cb0fd3f..a1cd01c78b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -14,12 +14,12 @@ namespace Avalonia.Diagnostics.ViewModels { internal class VisualTreeNode : TreeNode { - public VisualTreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null) + public VisualTreeNode(AvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null) : base(avaloniaObject, parent, customName) { Children = avaloniaObject switch { - IVisual visual => new VisualTreeNodeCollection(this, visual), + Visual visual => new VisualTreeNodeCollection(this, visual), Controls.Application host => new ApplicationHostVisuals(this, host), _ => TreeNodeCollection.Empty }; @@ -34,17 +34,17 @@ namespace Avalonia.Diagnostics.ViewModels public static VisualTreeNode[] Create(object control) { - return control is IAvaloniaObject visual ? + return control is AvaloniaObject visual ? new[] { new VisualTreeNode(visual, null) } : Array.Empty(); } internal class VisualTreeNodeCollection : TreeNodeCollection { - private readonly IVisual _control; + private readonly Visual _control; private readonly CompositeDisposable _subscriptions = new CompositeDisposable(2); - public VisualTreeNodeCollection(TreeNode owner, IVisual control) + public VisualTreeNodeCollection(TreeNode owner, Visual control) : base(owner) { _control = control; @@ -55,7 +55,7 @@ namespace Avalonia.Diagnostics.ViewModels _subscriptions.Dispose(); } - private static IObservable? GetHostedPopupRootObservable(IVisual visual) + private static IObservable? GetHostedPopupRootObservable(Visual visual) { static IObservable GetPopupHostObservable( IPopupHostProvider popupHostProvider, @@ -67,7 +67,7 @@ namespace Avalonia.Diagnostics.ViewModels .StartWith(popupHostProvider.PopupHost) .Select(popupHost => { - if (popupHost is IControl control) + if (popupHost is Control control) return new PopupRoot( control, providerName != null ? $"{providerName} ({control.GetType().Name})" : null); @@ -141,20 +141,20 @@ namespace Avalonia.Diagnostics.ViewModels _subscriptions.Add( _control.VisualChildren.ForEachItem( - (i, item) => nodes.Insert(i, new VisualTreeNode((IAvaloniaObject)item, Owner)), + (i, item) => nodes.Insert(i, new VisualTreeNode((AvaloniaObject)item, Owner)), (i, item) => nodes.RemoveAt(i), () => nodes.Clear())); } private struct PopupRoot { - public PopupRoot(IControl root, string? customName = null) + public PopupRoot(Control root, string? customName = null) { Root = root; CustomName = customName; } - public IControl Root { get; } + public Control Root { get; } public string? CustomName { get; } } } @@ -196,7 +196,7 @@ namespace Avalonia.Diagnostics.ViewModels { return; } - nodes.Add(new VisualTreeNode((IAvaloniaObject)s!,Owner)); + nodes.Add(new VisualTreeNode((AvaloniaObject)s!,Owner)); }), Window.WindowClosedEvent.AddClassHandler(typeof(Window), (s,e)=> { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs index d9e7ffe5ac..01e8503495 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs @@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.Views private void HistoryChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems?[0] is IControl control) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems?[0] is Control control) { DispatcherTimer.RunOnce(control.BringIntoView, TimeSpan.Zero); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs index 4f6b3f0aad..c81e3cadf4 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs @@ -100,7 +100,7 @@ namespace Avalonia.Diagnostics.Views } } - Point TranslateToRoot(Point point, IVisual from) + Point TranslateToRoot(Point point, Visual from) { return from.TranslatePoint(point, _layoutRoot) ?? default; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index e6e630112b..13b8cf5e8a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -39,7 +39,7 @@ namespace Avalonia.Diagnostics.Views .Subscribe(RawKeyDown); _pointerSubscription = InputManager.Instance?.Process .OfType() - .Subscribe(x => _lastPointerPosition = x.Root.PointToScreen(x.Position)); + .Subscribe(x => _lastPointerPosition = ((Visual)x.Root).PointToScreen(x.Position)); _frozenPopupStates = new Dictionary(); @@ -118,11 +118,11 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private IControl? GetHoveredControl(TopLevel topLevel) + private Control? GetHoveredControl(TopLevel topLevel) { var point = topLevel.PointToClient(_lastPointerPosition); - return (IControl?)topLevel.GetVisualsAt(point, x => + return (Control?)topLevel.GetVisualsAt(point, x => { if (x is AdornerLayer || !x.IsVisible) { @@ -138,7 +138,7 @@ namespace Avalonia.Diagnostics.Views { var popupRoots = new List(); - void ProcessProperty(IControl control, AvaloniaProperty property) + void ProcessProperty(Control control, AvaloniaProperty property) { if (control.GetValue(property) is IPopupHostProvider popupProvider && popupProvider.PopupHost is PopupRoot popupRoot) @@ -147,7 +147,7 @@ namespace Avalonia.Diagnostics.Views } } - foreach (var control in root.GetVisualDescendants().OfType()) + foreach (var control in root.GetVisualDescendants().OfType()) { if (control is Popup p && p.Host is PopupRoot popupRoot) { @@ -190,7 +190,7 @@ namespace Avalonia.Diagnostics.Views case RawInputModifiers.Shift when (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl): case RawInputModifiers.Shift | RawInputModifiers.Control: { - IControl? control = null; + Control? control = null; foreach (var popupRoot in GetPopupRoots(root)) { @@ -271,7 +271,7 @@ namespace Avalonia.Diagnostics.Views } } - internal void SelectedControl(IControl? control) + internal void SelectedControl(Control? control) { if (control is { }) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index a80ed2d4ef..092e26c96d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics /// Control to be rendered. /// Destination stream. /// Dpi quality. - public static void RenderTo(this IControl source, Stream destination, double dpi = 96) + public static void RenderTo(this Control source, Stream destination, double dpi = 96) { if (source.TransformedBounds == null) { @@ -29,7 +29,7 @@ namespace Avalonia.Diagnostics // get Visual root var root = (source.VisualRoot ?? source.GetVisualRoot()) - as IControl ?? source; + as Control ?? source; IDisposable? clipSetter = default; IDisposable? clipToBoundsSetter = default; diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs index 13c8e070e8..f34fe6d152 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs @@ -9,14 +9,14 @@ namespace Avalonia.Diagnostics { public static class VisualTreeDebug { - public static string PrintVisualTree(IVisual visual) + public static string PrintVisualTree(Visual visual) { var result = StringBuilderCache.Acquire(); PrintVisualTree(visual, result, 0); return StringBuilderCache.GetStringAndRelease(result); } - private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent) + private static void PrintVisualTree(Visual visual, StringBuilder builder, int indent) { Control? control = visual as Control; diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index a311efdfb0..99cf1538ae 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -16,4 +16,5 @@ + diff --git a/src/Avalonia.Dialogs/ManagedStorageProvider.cs b/src/Avalonia.Dialogs/ManagedStorageProvider.cs index 2c88251cca..e45d5c1b4a 100644 --- a/src/Avalonia.Dialogs/ManagedStorageProvider.cs +++ b/src/Avalonia.Dialogs/ManagedStorageProvider.cs @@ -29,7 +29,7 @@ public class ManagedStorageProvider : BclStorageProvider where T : Window, ne public override async Task> OpenFilePickerAsync(FilePickerOpenOptions options) { var model = new ManagedFileChooserViewModel(options, _managedOptions); - var results = await Show(model, _parent); + var results = await ManagedStorageProvider.Show(model, _parent); return results.Select(f => new BclStorageFile(new FileInfo(f))).ToArray(); } @@ -37,7 +37,7 @@ public class ManagedStorageProvider : BclStorageProvider where T : Window, ne public override async Task SaveFilePickerAsync(FilePickerSaveOptions options) { var model = new ManagedFileChooserViewModel(options, _managedOptions); - var results = await Show(model, _parent); + var results = await ManagedStorageProvider.Show(model, _parent); return results.FirstOrDefault() is { } result ? new BclStorageFile(new FileInfo(result)) @@ -47,12 +47,12 @@ public class ManagedStorageProvider : BclStorageProvider where T : Window, ne public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { var model = new ManagedFileChooserViewModel(options, _managedOptions); - var results = await Show(model, _parent); + var results = await ManagedStorageProvider.Show(model, _parent); return results.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray(); } - private async Task Show(ManagedFileChooserViewModel model, Window parent) + private static async Task Show(ManagedFileChooserViewModel model, Window parent) { var dialog = new T { diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index 4a55212de3..e37067d05c 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -127,7 +127,7 @@ namespace Avalonia.FreeDesktop var pid = Process.GetCurrentProcess().Id; var tid = s_trayIconInstanceId++; - _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; + _sysTrayServiceName = FormattableString.Invariant($"org.kde.StatusNotifierItem-{pid}-{tid}"); _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath); try diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs index 39ddd9d769..34c1506a67 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs @@ -34,14 +34,14 @@ namespace Avalonia.FreeDesktop Poll(0); } - private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); + private static string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); - private string UnescapeString(string input, string regexText, int escapeBase) => + private static string UnescapeString(string input, string regexText, int escapeBase) => new Regex(regexText).Replace(input, m => Convert.ToChar(Convert.ToByte(m.Groups[1].Value, escapeBase)).ToString()); - private string UnescapePathFromProcMounts(string input) => UnescapeString(input, @"\\(\d{3})", 8); + private static string UnescapePathFromProcMounts(string input) => UnescapeString(input, @"\\(\d{3})", 8); - private string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16); + private static string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16); private void Poll(long _) { diff --git a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj b/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj index 273910eb59..c713440dc9 100644 --- a/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj +++ b/src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj @@ -9,4 +9,5 @@ + diff --git a/src/Avalonia.Headless/Avalonia.Headless.csproj b/src/Avalonia.Headless/Avalonia.Headless.csproj index 2e33b5790a..95f7b79009 100644 --- a/src/Avalonia.Headless/Avalonia.Headless.csproj +++ b/src/Avalonia.Headless/Avalonia.Headless.csproj @@ -5,5 +5,9 @@ - + + + + + diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index 14dafb7284..b801f8401a 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -13,4 +13,6 @@ + + diff --git a/src/Avalonia.MicroCom/CallbackBase.cs b/src/Avalonia.MicroCom/CallbackBase.cs index 6c5ab225f6..5279569e83 100644 --- a/src/Avalonia.MicroCom/CallbackBase.cs +++ b/src/Avalonia.MicroCom/CallbackBase.cs @@ -38,7 +38,7 @@ namespace Avalonia.MicroCom } } - public MicroComShadow Shadow { get; set; } + public MicroComShadow? Shadow { get; set; } public void OnReferencedFromNative() { lock (_lock) diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index c924ef3a45..93fba4a57c 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -29,4 +29,5 @@ + diff --git a/src/Avalonia.Native/AvaloniaNativeDragSource.cs b/src/Avalonia.Native/AvaloniaNativeDragSource.cs index f91a299b3b..bec45c2d71 100644 --- a/src/Avalonia.Native/AvaloniaNativeDragSource.cs +++ b/src/Avalonia.Native/AvaloniaNativeDragSource.cs @@ -6,7 +6,6 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Native.Interop; -using Avalonia.Platform; using Avalonia.VisualTree; namespace Avalonia.Native @@ -19,15 +18,15 @@ namespace Avalonia.Native { _factory = factory; } - - TopLevel FindRoot(IInteractive interactive) + + private static TopLevel FindRoot(object? element) { - while (interactive != null && !(interactive is IVisual)) - interactive = interactive.InteractiveParent; - if (interactive == null) + while (element is Interactive interactive && element is not Visual) + element = interactive.GetInteractiveParent(); + if (element == null) return null; - var visual = (IVisual)interactive; - return visual.VisualRoot as TopLevel; + var visual = (Visual)element; + return visual.GetVisualRoot() as TopLevel; } class DndCallback : NativeCallbackBase, IAvnDndResultCallback diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index d8753efe25..7c7b32f7e4 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -65,7 +65,7 @@ namespace Avalonia.Native } } - private NativeMenu CreateDefaultAppMenu() + private static NativeMenu CreateDefaultAppMenu() { var result = new NativeMenu(); diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs index 7ce50c5126..3e46e0c5c6 100644 --- a/src/Avalonia.Native/IAvnMenu.cs +++ b/src/Avalonia.Native/IAvnMenu.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Reactive.Disposables; using Avalonia.Controls; -using Avalonia.Platform.Interop; namespace Avalonia.Native.Interop { @@ -112,7 +111,7 @@ namespace Avalonia.Native.Interop.Impl return result; } - private __MicroComIAvnMenuItemProxy CreateNew(IAvaloniaNativeFactory factory, NativeMenuItemBase item) + private static __MicroComIAvnMenuItemProxy CreateNew(IAvaloniaNativeFactory factory, NativeMenuItemBase item) { var nativeItem = (__MicroComIAvnMenuItemProxy)(item is NativeMenuItemSeparator ? factory.CreateMenuItemSeparator() : diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index b5af927ea0..9de794ed53 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -122,7 +122,7 @@ namespace Avalonia.Native { var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x => { - if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsVisible)) + if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) { return false; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index b74e4ec3b4..4744a20107 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -382,7 +382,7 @@ namespace Avalonia.Native return new DeferredRenderer(root, loop); } - return new ImmediateRenderer(root); + return new ImmediateRenderer((Visual)root); } public virtual void Dispose() diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index bacb10c792..a9136c2c99 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -12,4 +12,5 @@ + diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 279e7e750d..e13ee80864 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -38,7 +38,7 @@ namespace Avalonia.OpenGL.Controls base.Render(context); } - private void CheckError(GlInterface gl) + private static void CheckError(GlInterface gl) { int err; while ((err = gl.GetError()) != GL_NO_ERROR) @@ -197,7 +197,7 @@ namespace Avalonia.OpenGL.Controls } } - private bool CheckFramebufferStatus(GlInterface gl) + private static bool CheckFramebufferStatus(GlInterface gl) { var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 348f5f3a10..22ee548823 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -11,4 +11,5 @@ + diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs index 9f69b4ee6e..84048ffd49 100644 --- a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs +++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs @@ -17,7 +17,7 @@ namespace Avalonia.ReactiveUI /// public int GetAffinityForView(Type view) { - return typeof(IVisual).IsAssignableFrom(view) ? 10 : 0; + return typeof(Visual).IsAssignableFrom(view) ? 10 : 0; } /// @@ -25,7 +25,7 @@ namespace Avalonia.ReactiveUI /// public IObservable GetActivationForView(IActivatableView view) { - if (!(view is IVisual visual)) return Observable.Return(false); + if (!(view is Visual visual)) return Observable.Return(false); if (view is Control control) return GetActivationForControl(control); return GetActivationForVisual(visual); } @@ -55,7 +55,7 @@ namespace Avalonia.ReactiveUI /// Listens to AttachedToVisualTree and DetachedFromVisualTree /// events for Avalonia IVisuals. /// - private IObservable GetActivationForVisual(IVisual visual) + private IObservable GetActivationForVisual(Visual visual) { var visualLoaded = Observable .FromEventPattern( diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index 0750fef067..dc45758046 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -121,7 +121,7 @@ namespace Avalonia.ReactiveUI } viewInstance.ViewModel = viewModel; - if (viewInstance is IStyledElement styled) + if (viewInstance is StyledElement styled) styled.DataContext = viewModel; Content = viewInstance; } diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index d746c6db7e..baeb8820f8 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -8,6 +8,8 @@ + + \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index c58aafa4e0..c18a76711e 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -6,6 +7,7 @@ using Metsys.Bson; namespace Avalonia.Remote.Protocol { + [RequiresUnreferencedCode("Bson uses reflection")] class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection { private readonly IMessageTypeResolver _resolver; diff --git a/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs index e647f75997..040f90e4bd 100644 --- a/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; namespace Avalonia.Remote.Protocol { + [RequiresUnreferencedCode("Bson uses reflection")] public class BsonTcpTransport : TcpTransportBase { public BsonTcpTransport(IMessageTypeResolver resolver) : base(resolver) @@ -24,4 +26,4 @@ namespace Avalonia.Remote.Protocol return wrap; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs index 8e17dbccfd..3e8b90c741 100644 --- a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs +++ b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -9,6 +10,9 @@ namespace Avalonia.Remote.Protocol { private readonly Dictionary _guidsToTypes = new Dictionary(); private readonly Dictionary _typesToGuids = new Dictionary(); + + [UnconditionalSuppressMessage("Trimming", "IL2026", + Justification = "If type was trimmed, we don't need to resolve it in the remove protocol")] public DefaultMessageTypeResolver(params Assembly[] assemblies) { foreach (var asm in diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 6abece6bf3..d011a963be 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -32,6 +32,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -71,7 +72,7 @@ namespace Metsys.Bson namespace Metsys.Bson { - + [RequiresUnreferencedCode("Bson uses reflection")] internal class Serializer { private static readonly IDictionary _typeMap = new Dictionary @@ -562,7 +563,7 @@ namespace Metsys.Bson { if (_string == null && Value != null) { - _string = BitConverter.ToString(Value).Replace("-", string.Empty).ToLower(); + _string = BitConverter.ToString(Value).Replace("-", string.Empty).ToLowerInvariant(); } return _string; @@ -687,6 +688,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class TypeHelper { private static readonly IDictionary _cachedTypeLookup = new Dictionary(); @@ -787,6 +789,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class ListWrapper : BaseWrapper { private IList _list; @@ -821,6 +824,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal static class ListHelper { public static Type GetListItemType(Type enumerableType) @@ -865,6 +869,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class CollectionWrapper : BaseWrapper { private ICollection _list; @@ -892,6 +897,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal abstract class BaseWrapper { public static BaseWrapper Create(Type type, Type itemType, object existingContainer) @@ -948,7 +954,7 @@ namespace Metsys.Bson namespace Metsys.Bson { - + [RequiresUnreferencedCode("Bson uses reflection")] internal class ArrayWrapper : BaseWrapper { private readonly List _list = new List(); @@ -1000,6 +1006,7 @@ namespace Metsys.Bson namespace Metsys.Bson { + [RequiresUnreferencedCode("Bson uses reflection")] internal class Deserializer { internal class Options diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index 597fab22f8..ab543703d9 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -1,6 +1,8 @@  net6.0;netstandard2.0 + + $(NoWarn);IL2026 @@ -14,4 +16,5 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml index 06b6cf30c2..76b51ca819 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml @@ -35,13 +35,13 @@ - + @@ -70,7 +70,7 @@ - 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/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(@" - +