diff --git a/.editorconfig b/.editorconfig index a144ec8843..d07618df6c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -186,6 +186,23 @@ csharp_wrap_before_ternary_opsigns = false # Avalonia DevAnalyzer preferences dotnet_diagnostic.AVADEV2001.severity = error +# Avalonia PublicAnalyzer preferences +dotnet_diagnostic.AVP1000.severity = error +dotnet_diagnostic.AVP1001.severity = error +dotnet_diagnostic.AVP1002.severity = error +dotnet_diagnostic.AVP1010.severity = error +dotnet_diagnostic.AVP1011.severity = error +dotnet_diagnostic.AVP1012.severity = warning +dotnet_diagnostic.AVP1013.severity = error +dotnet_diagnostic.AVP1020.severity = error +dotnet_diagnostic.AVP1021.severity = error +dotnet_diagnostic.AVP1022.severity = error +dotnet_diagnostic.AVP1030.severity = error +dotnet_diagnostic.AVP1031.severity = error +dotnet_diagnostic.AVP1032.severity = error +dotnet_diagnostic.AVP1040.severity = error +dotnet_diagnostic.AVA2001.severity = error + # Xaml files [*.{xaml,axaml}] indent_size = 2 diff --git a/Avalonia.sln b/Avalonia.sln index d4ccdfdc69..ee3bc05f0e 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -233,7 +233,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\R EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\PublicAnalyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\Avalonia.Analyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}" ProjectSection(SolutionItems) = preProject diff --git a/build/DevAnalyzers.props b/build/DevAnalyzers.props index 7d021d051f..dffd3098c3 100644 --- a/build/DevAnalyzers.props +++ b/build/DevAnalyzers.props @@ -5,7 +5,7 @@ ReferenceOutputAssembly="false" OutputItemType="Analyzer" SetTargetFramework="TargetFramework=netstandard2.0"/> - + var forceUnstable = type.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute"); foreach (var m in type.Methods) @@ -109,22 +109,28 @@ public class RefAssemblyGenerator } } - static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force) + static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute unstableAttribute) { - if (!force && ( - def.HasCustomAttributes == false - || def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute"))) + if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute")) return; - if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute")) + unstableAttribute = def.CustomAttributes.FirstOrDefault(a => + a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute") ?? unstableAttribute; + + if (unstableAttribute is null) return; + var message = unstableAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString(); + if (string.IsNullOrEmpty(message)) + { + message = "This is a part of unstable API and can be changed in minor releases. Consider replacing it with alternatives or reach out developers on GitHub."; + } + def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor) { ConstructorArguments = { - new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String, - "This is a part of unstable API and can be changed in minor releases. You have been warned") + new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String, message) } }); } @@ -168,4 +174,4 @@ public class RefAssemblyGenerator } } } -} \ No newline at end of file +} diff --git a/nukebuild/numerge.config b/nukebuild/numerge.config index 09f22ec527..71b77bee93 100644 --- a/nukebuild/numerge.config +++ b/nukebuild/numerge.config @@ -16,6 +16,11 @@ "Id": "Avalonia.Generators", "IgnoreMissingFrameworkBinaries": true, "DoNotMergeDependencies": true + }, + { + "Id": "Avalonia.Analyzers", + "IgnoreMissingFrameworkBinaries": true, + "DoNotMergeDependencies": true } ] } diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 547af5d0dc..412251bc9c 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -16,6 +16,10 @@ ReferenceOutputAssembly="false" PrivateAssets="all" OutputItemType="Analyzer" /> + diff --git a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs index 549cf3d740..782435ae06 100644 --- a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs +++ b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs @@ -19,18 +19,16 @@ namespace ControlCatalog.Pages public static readonly StyledProperty ScaleProperty = AvaloniaProperty.Register(nameof(Scale), 1.0d); public double Scale { get => GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); } - public static readonly StyledProperty RotationProperty = AvaloniaProperty.Register(nameof(Rotation)); + public static readonly StyledProperty RotationProperty = AvaloniaProperty.Register(nameof(Rotation), + coerce: (_, val) => val % (Math.PI * 2)); + /// /// Rotation, measured in Radians! /// public double Rotation { get => GetValue(RotationProperty); - set - { - double valueToUse = value % (Math.PI * 2); - SetValue(RotationProperty, valueToUse); - } + set => SetValue(RotationProperty, value); } public static readonly StyledProperty ViewportCenterYProperty = AvaloniaProperty.Register(nameof(ViewportCenterY), 0.0d); @@ -213,5 +211,6 @@ namespace ControlCatalog.Pages return workingPoint; } + } } diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj index 1356eeb526..398743a353 100644 --- a/samples/IntegrationTestApp/IntegrationTestApp.csproj +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -3,6 +3,7 @@ WinExe net7.0 enable + $(NoWarn);AVP1012 diff --git a/samples/RenderDemo/Pages/CustomSkiaPage.cs b/samples/RenderDemo/Pages/CustomSkiaPage.cs index 4a3e20ff5b..2d64ba8f7b 100644 --- a/samples/RenderDemo/Pages/CustomSkiaPage.cs +++ b/samples/RenderDemo/Pages/CustomSkiaPage.cs @@ -44,9 +44,9 @@ namespace RenderDemo.Pages public bool HitTest(Point p) => false; public bool Equals(ICustomDrawOperation other) => false; static Stopwatch St = Stopwatch.StartNew(); - public void Render(IDrawingContextImpl context) + public void Render(ImmediateDrawingContext context) { - var leaseFeature = context.GetFeature(); + var leaseFeature = context.TryGetFeature(); if (leaseFeature == null) context.DrawGlyphRun(Brushes.Black, _noSkia.PlatformImpl); else diff --git a/samples/RenderDemo/Pages/WriteableBitmapPage.cs b/samples/RenderDemo/Pages/WriteableBitmapPage.cs index 850e398a93..a13a625d14 100644 --- a/samples/RenderDemo/Pages/WriteableBitmapPage.cs +++ b/samples/RenderDemo/Pages/WriteableBitmapPage.cs @@ -59,7 +59,7 @@ namespace RenderDemo.Pages color = new Color(fillAlpha, r, g, b); } - data[y * fb.Size.Width + x] = (int) color.ToUint32(); + data[y * fb.Size.Width + x] = (int) color.ToUInt32(); } } diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index d62acc0d52..dd99c40cd3 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/src/Avalonia.Base/Animation/Animation.cs @@ -200,7 +200,7 @@ namespace Avalonia.Animation /// /// The animation setter. /// The property animator value. - public static void SetAnimator(IAnimationSetter setter, + public static void SetAnimator(IAnimationSetter setter, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)] Type value) { @@ -319,7 +319,7 @@ namespace Avalonia.Animation if (animators.Count == 1) { var subscription = animators[0].Apply(this, control, clock, match, onComplete); - + if (subscription is not null) { subscriptions.Add(subscription); @@ -348,9 +348,11 @@ namespace Avalonia.Animation if (onComplete != null) { - Task.WhenAll(completionTasks!).ContinueWith( - (_, state) => ((Action)state!).Invoke(), - onComplete); + Task.WhenAll(completionTasks!) + .ContinueWith((_, state) => ((Action)state!).Invoke() + , onComplete + , TaskScheduler.FromCurrentSynchronizationContext() + ); } } return new CompositeDisposable(subscriptions); diff --git a/src/Avalonia.Base/ClassBindingManager.cs b/src/Avalonia.Base/ClassBindingManager.cs index a9726cb86e..55f3a7892a 100644 --- a/src/Avalonia.Base/ClassBindingManager.cs +++ b/src/Avalonia.Base/ClassBindingManager.cs @@ -17,6 +17,8 @@ namespace Avalonia return target.Bind(prop, source, anchor); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1001:The same AvaloniaProperty should not be registered twice", + Justification = "Classes.attr binding feature is implemented using intermediate avalonia properties for each class")] private static AvaloniaProperty RegisterClassProxyProperty(string className) { var prop = AvaloniaProperty.Register("__AvaloniaReserved::Classes::" + className); diff --git a/src/Avalonia.Base/Controls/IThemeVariantProvider.cs b/src/Avalonia.Base/Controls/IThemeVariantProvider.cs index d1dca2efbf..03a7fb1206 100644 --- a/src/Avalonia.Base/Controls/IThemeVariantProvider.cs +++ b/src/Avalonia.Base/Controls/IThemeVariantProvider.cs @@ -10,9 +10,8 @@ namespace Avalonia.Controls; /// /// This is a helper interface for the XAML compiler to make Key property accessibly by the markup extensions. /// Which means, it can only be used with ResourceDictionaries and markup extensions in the XAML code. -/// This API might be removed in the future minor updates. /// -[Unstable] +[Unstable("This XAML-only API might be removed in the future minor updates.")] public interface IThemeVariantProvider : IResourceProvider { /// diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 8d7cc2b9a1..26ec98361b 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -25,8 +25,7 @@ namespace Avalonia.Input return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0); } - [Unstable] - [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")] + [Unstable("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")] public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) : base(routedEvent) { diff --git a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs index c405cdfacd..3c4562edf4 100644 --- a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs @@ -9,8 +9,7 @@ namespace Avalonia.Input { public Vector Delta { get; } - [Unstable] - [Obsolete("This constructor might be removed in 12.0.")] + [Unstable("This constructor might be removed in 12.0.")] public PointerDeltaEventArgs(RoutedEvent routedEvent, object? source, IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index beb953ce8f..7f82199b56 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -14,8 +14,7 @@ namespace Avalonia.Input private readonly PointerPointProperties _properties; private readonly Lazy?>? _previousPoints; - [Unstable] - [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")] + [Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")] public PointerEventArgs(RoutedEvent routedEvent, object? source, IPointer pointer, @@ -129,8 +128,7 @@ namespace Avalonia.Input public class PointerPressedEventArgs : PointerEventArgs { - [Unstable] - [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")] + [Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")] public PointerPressedEventArgs( object source, IPointer pointer, @@ -150,8 +148,7 @@ namespace Avalonia.Input public class PointerReleasedEventArgs : PointerEventArgs { - [Unstable] - [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")] + [Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")] public PointerReleasedEventArgs( object source, IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp, @@ -173,8 +170,7 @@ namespace Avalonia.Input { public IPointer Pointer { get; } - [Unstable] - [Obsolete("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")] + [Unstable("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")] public PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) { Pointer = pointer; diff --git a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs index 903019d85d..22624a61dd 100644 --- a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs @@ -9,8 +9,7 @@ namespace Avalonia.Input { public Vector Delta { get; } - [Unstable] - [Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow.MouseWheel.")] + [Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow.MouseWheel.")] public PointerWheelEventArgs(object source, IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index 747ee1c082..f47738f2e4 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -249,10 +249,12 @@ namespace Avalonia.Layout { var control = _toMeasure.Dequeue(); - if (!control.IsMeasureValid && control.IsAttachedToVisualTree) + if (!control.IsMeasureValid) { Measure(control); } + + _toArrange.Enqueue(control); } } @@ -262,7 +264,7 @@ namespace Avalonia.Layout { var control = _toArrange.Dequeue(); - if (!control.IsArrangeValid && control.IsAttachedToVisualTree) + if (!control.IsArrangeValid) { Arrange(control); } @@ -297,8 +299,6 @@ namespace Avalonia.Layout { control.Measure(control.PreviousMeasure.Value); } - - _toArrange.Enqueue(control); } return true; diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 7b29ec640a..56a3b0d7a5 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -449,7 +449,7 @@ namespace Avalonia.Media /// public override string ToString() { - uint rgb = ToUint32(); + uint rgb = ToUInt32(); return KnownColors.GetKnownColorName(rgb) ?? $"#{rgb.ToString("x8", CultureInfo.InvariantCulture)}"; } @@ -459,11 +459,18 @@ namespace Avalonia.Media /// /// The integer representation of the color. /// - public uint ToUint32() + public uint ToUInt32() { return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B; } + /// + [Obsolete("Use Color.ToUInt32() instead.")] + public uint ToUint32() + { + return ToUInt32(); + } + /// /// Returns the HSL color model equivalent of this RGB color. /// diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs index 4749bfa401..2529b9317d 100644 --- a/src/Avalonia.Base/Media/DashStyle.cs +++ b/src/Avalonia.Base/Media/DashStyle.cs @@ -44,6 +44,8 @@ namespace Avalonia.Media /// /// The dashes collection. /// The dash sequence offset. + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", + Justification = "Collection properties shouldn't be set with SetCurrentValue.")] public DashStyle(IEnumerable? dashes, double offset) { Dashes = (dashes as AvaloniaList) ?? new AvaloniaList(dashes ?? Array.Empty()); diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs index abfd2e33ac..75d7e44ab8 100644 --- a/src/Avalonia.Base/Media/GeometryDrawing.cs +++ b/src/Avalonia.Base/Media/GeometryDrawing.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media public class GeometryDrawing : Drawing { // Adding the Pen's stroke thickness here could yield wrong results due to transforms. - private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUint32(), 0); + private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUInt32(), 0); /// /// Defines the property. diff --git a/src/Avalonia.Base/Media/GradientBrush.cs b/src/Avalonia.Base/Media/GradientBrush.cs index e1654a01b2..971d4fdd58 100644 --- a/src/Avalonia.Base/Media/GradientBrush.cs +++ b/src/Avalonia.Base/Media/GradientBrush.cs @@ -38,6 +38,8 @@ namespace Avalonia.Media /// /// Initializes a new instance of the class. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", + Justification = "Collection properties shouldn't be set with SetCurrentValue.")] public GradientBrush() { this.GradientStops = new GradientStops(); diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index b4bf6fd217..897c883875 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -98,43 +98,13 @@ namespace Avalonia.Media L = hsl.L; } - /// - /// Gets the Alpha (transparency) component in the range from 0..1 (percentage). - /// - /// - /// - /// 0 is fully transparent. - /// 1 is fully opaque. - /// - /// + /// public double A { get; } - /// - /// Gets the Hue component in the range from 0..360 (degrees). - /// This is the color's location, in degrees, on a color wheel/circle from 0 to 360. - /// Note that 360 is equivalent to 0 and will be adjusted automatically. - /// - /// - /// - /// 0/360 degrees is Red. - /// 60 degrees is Yellow. - /// 120 degrees is Green. - /// 180 degrees is Cyan. - /// 240 degrees is Blue. - /// 300 degrees is Magenta. - /// - /// + /// public double H { get; } - /// - /// Gets the Saturation component in the range from 0..1 (percentage). - /// - /// - /// - /// 0 is a shade of gray (no color). - /// 1 is the full color. - /// - /// + /// public double S { get; } /// diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs index 58b153482d..17c4560523 100644 --- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Media.Imaging; diff --git a/src/Avalonia.Base/Media/PlatformDrawingContext.cs b/src/Avalonia.Base/Media/PlatformDrawingContext.cs index eb8a93722c..4b683c6acb 100644 --- a/src/Avalonia.Base/Media/PlatformDrawingContext.cs +++ b/src/Avalonia.Base/Media/PlatformDrawingContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Avalonia.Logging; using Avalonia.Media.Imaging; using Avalonia.Media.Immutable; using Avalonia.Platform; @@ -41,8 +42,19 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) => _impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode); - public override void Custom(ICustomDrawOperation custom) => - custom.Render(_impl); + public override void Custom(ICustomDrawOperation custom) + { + using var immediateDrawingContext = new ImmediateDrawingContext(_impl, false); + try + { + custom.Render(immediateDrawingContext); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual) + ?.Log(custom, $"Exception in {custom.GetType().Name}.{nameof(ICustomDrawOperation.Render)} {{0}}", e); + } + } public override void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Base/Media/PolyLineSegment.cs b/src/Avalonia.Base/Media/PolyLineSegment.cs index d17a621348..51bf13d7cb 100644 --- a/src/Avalonia.Base/Media/PolyLineSegment.cs +++ b/src/Avalonia.Base/Media/PolyLineSegment.cs @@ -28,6 +28,8 @@ namespace Avalonia.Media /// /// Initializes a new instance of the class. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", + Justification = "Collection properties shouldn't be set with SetCurrentValue.")] public PolyLineSegment() { Points = new Points(); diff --git a/src/Avalonia.Base/Media/TransformGroup.cs b/src/Avalonia.Base/Media/TransformGroup.cs index 0465efd5a5..ae5e54c414 100644 --- a/src/Avalonia.Base/Media/TransformGroup.cs +++ b/src/Avalonia.Base/Media/TransformGroup.cs @@ -11,6 +11,8 @@ namespace Avalonia.Media public static readonly StyledProperty ChildrenProperty = AvaloniaProperty.Register(nameof(Children)); + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", + Justification = "Collection properties shouldn't be set with SetCurrentValue.")] public TransformGroup() { Children = new Transforms(); diff --git a/src/Avalonia.Base/Metadata/UnstableAttribute.cs b/src/Avalonia.Base/Metadata/UnstableAttribute.cs index 361f6d30fd..bbb298f7a6 100644 --- a/src/Avalonia.Base/Metadata/UnstableAttribute.cs +++ b/src/Avalonia.Base/Metadata/UnstableAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Avalonia.Metadata { @@ -9,5 +9,25 @@ namespace Avalonia.Metadata [AttributeUsage(AttributeTargets.All)] public sealed class UnstableAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + public UnstableAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The text string that describes alternative workarounds. + public UnstableAttribute(string? message) + { + Message = message; + } + + /// + /// Gets a value that indicates whether the compiler will treat usage of the obsolete program element as an error. + /// + public string? Message { get; } } } diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs index 7df446e854..854610f1c9 100644 --- a/src/Avalonia.Base/Platform/AssetLoader.cs +++ b/src/Avalonia.Base/Platform/AssetLoader.cs @@ -1,281 +1,48 @@ -using System; +using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; 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 +namespace Avalonia.Platform; + #if !BUILDTASK - : IAssetLoader +/// #endif - { +public static class AssetLoader +{ #if !BUILDTASK - private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver(); - - private AssemblyDescriptor? _defaultResmAssembly; - - /// - /// Introduced for tests. - /// - internal static void SetAssemblyDescriptorResolver(IAssemblyDescriptorResolver resolver) => - s_assemblyDescriptorResolver = resolver; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The default assembly from which to load resm: assets for which no assembly is specified. - /// - public AssetLoader(Assembly? assembly = null) - { - if (assembly == null) - assembly = Assembly.GetEntryAssembly(); - if (assembly != null) - _defaultResmAssembly = new AssemblyDescriptor(assembly); - } - - /// - /// Sets the default assembly from which to load assets for which no assembly is specified. - /// - /// The default assembly. - public void SetDefaultAssembly(Assembly assembly) - { - _defaultResmAssembly = new AssemblyDescriptor(assembly); - } - - /// - /// Checks if an asset with the specified URI exists. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// True if the asset could be found; otherwise false. - public bool Exists(Uri uri, Uri? baseUri = null) - { - return TryGetAsset(uri, baseUri, out _); - } - - /// - /// Opens the asset with the requested URI. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// A stream containing the asset contents. - /// - /// The asset could not be found. - /// - public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; - - /// - /// Opens the asset with the requested URI and returns the asset stream and the - /// assembly containing the asset. - /// - /// The URI. - /// - /// A base URI to use if is relative. - /// - /// - /// The stream containing the resource contents together with the assembly. - /// - /// - /// The asset could not be found. - /// - public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) - { - if (TryGetAsset(uri, baseUri, out var assetDescriptor)) - { - return (assetDescriptor.GetStream(), assetDescriptor.Assembly); - } - - throw new FileNotFoundException($"The resource {uri} could not be found."); - } - - public Assembly? GetAssembly(Uri uri, Uri? baseUri) - { - if (!uri.IsAbsoluteUri && baseUri != null) - { - uri = new Uri(baseUri, uri); - } - - if (TryGetAssembly(uri, out var assemblyDescriptor)) - { - return assemblyDescriptor.Assembly; - } - - return null; - } - - /// - /// Gets all assets of a folder and subfolders that match specified uri. - /// - /// The URI. - /// Base URI that is used if is relative. - /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset - public IEnumerable GetAssets(Uri uri, Uri? baseUri) - { - if (uri.IsAbsoluteResm()) - { - if (!TryGetAssembly(uri, out var assembly)) - { - assembly = _defaultResmAssembly; - } - - return assembly?.Resources? - .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) - .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? - Enumerable.Empty(); - } - - uri = uri.EnsureAbsolute(baseUri); - - if (uri.IsAvares()) - { - if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) - { - return Enumerable.Empty(); - } + private static IAssetLoader GetAssetLoader() => AvaloniaLocator.Current.GetRequiredService(); - if (assembly?.AvaloniaResources == null) - { - return Enumerable.Empty(); - } + /// + public static void SetDefaultAssembly(Assembly assembly) => GetAssetLoader().SetDefaultAssembly(assembly); - if (path.Length > 0 && path[path.Length - 1] != '/') - { - path += '/'; - } + /// + public static bool Exists(Uri uri, Uri? baseUri = null) => GetAssetLoader().Exists(uri, baseUri); - return assembly.AvaloniaResources - .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) - .Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); - } + /// + public static Stream Open(Uri uri, Uri? baseUri = null) => GetAssetLoader().Open(uri, baseUri); - return Enumerable.Empty(); - } + /// + public static (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) + => GetAssetLoader().OpenAndGetAssembly(uri, baseUri); - private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) - { - assetDescriptor = null; + /// + public static Assembly? GetAssembly(Uri uri, Uri? baseUri = null) + => GetAssetLoader().GetAssembly(uri, baseUri); - if (uri.IsAbsoluteResm()) - { - if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) - { - assembly = _defaultResmAssembly; - } - - if (assembly?.Resources != null) - { - var resourceKey = uri.AbsolutePath; - - if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) - { - return true; - } - } - } - - uri = uri.EnsureAbsolute(baseUri); - - if (uri.IsAvares()) - { - if (TryGetResAsmAndPath(uri, out var assembly, out var path)) - { - if (assembly.AvaloniaResources == null) - { - return false; - } - - if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) - { - return true; - } - } - } - - return false; - } - - private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) - { - path = uri.GetUnescapeAbsolutePath(); - - if (TryLoadAssembly(uri.Authority, out assembly)) - { - return true; - } - - return false; - } - - private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) - { - assembly = null; - - if (uri != null) - { - if (!uri.IsAbsoluteUri) - { - return false; - } - - if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) - { - return true; - } - - if (uri.IsResm()) - { - var assemblyName = uri.GetAssemblyNameFromQuery(); - - if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) - { - return true; - } - } - } - - return false; - } - - private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) - { - assembly = null; - - try - { - assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName); - - return true; - } - catch (Exception) { } - - return false; - } + /// + public static IEnumerable GetAssets(Uri uri, Uri? baseUri) + => GetAssetLoader().GetAssets(uri, baseUri); #endif - public static void RegisterResUriParsers() - { - if (!UriParser.IsKnownScheme("avares")) - UriParser.Register(new GenericUriParser( - GenericUriParserOptions.GenericAuthority | - GenericUriParserOptions.NoUserInfo | - GenericUriParserOptions.NoPort | - GenericUriParserOptions.NoQuery | - GenericUriParserOptions.NoFragment), "avares", -1); - } + internal static void RegisterResUriParsers() + { + if (!UriParser.IsKnownScheme("avares")) + UriParser.Register(new GenericUriParser( + GenericUriParserOptions.GenericAuthority | + GenericUriParserOptions.NoUserInfo | + GenericUriParserOptions.NoPort | + GenericUriParserOptions.NoQuery | + GenericUriParserOptions.NoFragment), "avares", -1); } } diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index b65d61803f..f1ce624c70 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -9,7 +9,7 @@ namespace Avalonia.Platform /// /// Loads assets compiled into the application binary. /// - [Unstable] + [Unstable("IAssetLoader interface and AvaloniaLocator usage is considered unstable. Please use AssetLoader static class instead.")] public interface IAssetLoader { /// diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index 1359ad6603..ef53024508 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -1,6 +1,5 @@ using System; using Avalonia.Media; -using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Avalonia.Media.Imaging; using Avalonia.Metadata; @@ -168,12 +167,6 @@ namespace Avalonia.Platform /// void PopBitmapBlendMode(); - /// - /// Adds a custom draw operation - /// - /// Custom draw operation - void Custom(ICustomDrawOperation custom); - /// /// Attempts to get an optional feature from the drawing context implementation /// diff --git a/src/Avalonia.Base/Platform/StandardAssetLoader.cs b/src/Avalonia.Base/Platform/StandardAssetLoader.cs new file mode 100644 index 0000000000..118e57c7af --- /dev/null +++ b/src/Avalonia.Base/Platform/StandardAssetLoader.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using Avalonia.Platform.Internal; +using Avalonia.Utilities; + +namespace Avalonia.Platform; + +/// +/// Loads assets compiled into the application binary. +/// +internal class StandardAssetLoader : IAssetLoader +{ + private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver; + private AssemblyDescriptor? _defaultResmAssembly; + + public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) + { + if (assembly == null) + assembly = Assembly.GetEntryAssembly(); + if (assembly != null) + _defaultResmAssembly = new AssemblyDescriptor(assembly); + _assemblyDescriptorResolver = resolver; + } + + public StandardAssetLoader(Assembly? assembly = null) : this(new AssemblyDescriptorResolver(), assembly) + { + + } + + /// + /// Sets the default assembly from which to load assets for which no assembly is specified. + /// + /// The default assembly. + public void SetDefaultAssembly(Assembly assembly) + { + _defaultResmAssembly = new AssemblyDescriptor(assembly); + } + + /// + /// Checks if an asset with the specified URI exists. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// True if the asset could be found; otherwise false. + public bool Exists(Uri uri, Uri? baseUri = null) + { + return TryGetAsset(uri, baseUri, out _); + } + + /// + /// Opens the asset with the requested URI. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// A stream containing the asset contents. + /// + /// The asset could not be found. + /// + public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; + + /// + /// Opens the asset with the requested URI and returns the asset stream and the + /// assembly containing the asset. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The asset could not be found. + /// + public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) + { + if (TryGetAsset(uri, baseUri, out var assetDescriptor)) + { + return (assetDescriptor.GetStream(), assetDescriptor.Assembly); + } + + throw new FileNotFoundException($"The resource {uri} could not be found."); + } + + public Assembly? GetAssembly(Uri uri, Uri? baseUri) + { + if (!uri.IsAbsoluteUri && baseUri != null) + { + uri = new Uri(baseUri, uri); + } + + if (TryGetAssembly(uri, out var assemblyDescriptor)) + { + return assemblyDescriptor.Assembly; + } + + return null; + } + + /// + /// Gets all assets of a folder and subfolders that match specified uri. + /// + /// The URI. + /// Base URI that is used if is relative. + /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset + public IEnumerable GetAssets(Uri uri, Uri? baseUri) + { + if (uri.IsAbsoluteResm()) + { + if (!TryGetAssembly(uri, out var assembly)) + { + assembly = _defaultResmAssembly; + } + + return assembly?.Resources? + .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) + .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? + Enumerable.Empty(); + } + + uri = uri.EnsureAbsolute(baseUri); + + if (uri.IsAvares()) + { + if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) + { + return Enumerable.Empty(); + } + + if (assembly?.AvaloniaResources == null) + { + return Enumerable.Empty(); + } + + if (path.Length > 0 && path[path.Length - 1] != '/') + { + path += '/'; + } + + return assembly.AvaloniaResources + .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) + .Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); + } + + return Enumerable.Empty(); + } + + private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) + { + assetDescriptor = null; + + if (uri.IsAbsoluteResm()) + { + if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) + { + assembly = _defaultResmAssembly; + } + + if (assembly?.Resources != null) + { + var resourceKey = uri.AbsolutePath; + + if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) + { + return true; + } + } + } + + uri = uri.EnsureAbsolute(baseUri); + + if (uri.IsAvares()) + { + if (TryGetResAsmAndPath(uri, out var assembly, out var path)) + { + if (assembly.AvaloniaResources == null) + { + return false; + } + + if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) + { + return true; + } + } + } + + return false; + } + + private bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) + { + path = uri.GetUnescapeAbsolutePath(); + + if (TryLoadAssembly(uri.Authority, out assembly)) + { + return true; + } + + return false; + } + + private bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) + { + assembly = null; + + if (uri != null) + { + if (!uri.IsAbsoluteUri) + { + return false; + } + + if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) + { + return true; + } + + if (uri.IsResm()) + { + var assemblyName = uri.GetAssemblyNameFromQuery(); + + if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) + { + return true; + } + } + } + + return false; + } + + private bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) + { + assembly = null; + + try + { + assembly = _assemblyDescriptorResolver.GetAssembly(assemblyName); + + return true; + } + catch (Exception) { } + + return false; + } +} diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs index 0a36b4c9dd..800d9b390f 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs @@ -14,7 +14,7 @@ namespace Avalonia.Platform AssetLoader.RegisterResUriParsers(); AvaloniaLocator.CurrentMutable .Bind().ToConstant(standardPlatform) - .Bind().ToConstant(new AssetLoader(assembly)) + .Bind().ToConstant(new StandardAssetLoader(assembly)) .Bind().ToConstant( #if NET6_0_OR_GREATER new Net6Loader() diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 1ec1362a4c..901bdaae0d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -143,11 +143,6 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, _impl.PopBitmapBlendMode(); } - public void Custom(ICustomDrawOperation custom) - { - _impl.Custom(custom); - } - public object? GetFeature(Type t) => _impl.GetFeature(t); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs b/src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs index 8f5ccb4e51..7ce9e6a8af 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Logging; using Avalonia.Media; using Avalonia.Platform; @@ -17,7 +18,16 @@ namespace Avalonia.Rendering.SceneGraph public override void Render(IDrawingContextImpl context) { - Custom.Render(context); + using var immediateDrawingContext = new ImmediateDrawingContext(context, false); + try + { + Custom.Render(immediateDrawingContext); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual) + ?.Log(Custom, $"Exception in {Custom.GetType().Name}.{nameof(ICustomDrawOperation.Render)} {{0}}", e); + } } public override void Dispose() => Custom.Dispose(); @@ -48,6 +58,6 @@ namespace Avalonia.Rendering.SceneGraph /// Renders the node to a drawing context. /// /// The drawing context. - void Render(IDrawingContextImpl context); + void Render(ImmediateDrawingContext context); } } diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 5881efce1e..3270fe4614 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -289,6 +289,7 @@ namespace Avalonia public StyledElement? Parent { get; private set; } /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030:StyledProperty accessors should not have side effects", Justification = "False positive?")] public ThemeVariant ActualThemeVariant => GetValue(ThemeVariant.ActualThemeVariantProperty); /// diff --git a/src/Avalonia.Base/Styling/ThemeVariant.cs b/src/Avalonia.Base/Styling/ThemeVariant.cs index 389136b0f5..23bc15dfa7 100644 --- a/src/Avalonia.Base/Styling/ThemeVariant.cs +++ b/src/Avalonia.Base/Styling/ThemeVariant.cs @@ -9,6 +9,10 @@ namespace Avalonia.Styling; /// Specifies a UI theme variant that should be used for the Control and Application types. /// [TypeConverter(typeof(ThemeVariantTypeConverter))] +[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010:AvaloniaProperty objects should be owned by the type in which they are stored", + Justification = "ActualThemeVariant and RequestedThemeVariant properties are shared Avalonia.Base and Avalonia.Controls projects," + + "but shouldn't be visible on the StyledElement class." + + "Ideally we woould introduce readonly styled properties.")] public sealed record ThemeVariant { /// diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs index 6842e4a255..699186868a 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -118,11 +117,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func synchronously on the + /// Executes the specified Func<TResult> synchronously on the /// thread that the Dispatcher was created on. /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<TResult> delegate to invoke through the dispatcher. /// /// /// The return value from the delegate being invoked. @@ -136,11 +135,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func synchronously on the + /// Executes the specified Func<TResult> synchronously on the /// thread that the Dispatcher was created on. /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<TResult> delegate to invoke through the dispatcher. /// /// /// The priority that determines in what order the specified @@ -156,11 +155,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func synchronously on the + /// Executes the specified Func<TResult> synchronously on the /// thread that the Dispatcher was created on. /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<TResult> delegate to invoke through the dispatcher. /// /// /// The priority that determines in what order the specified @@ -183,11 +182,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func synchronously on the + /// Executes the specified Func<TResult> synchronously on the /// thread that the Dispatcher was created on. /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<TResult> delegate to invoke through the dispatcher. /// /// /// The priority that determines in what order the specified @@ -317,11 +316,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func asynchronously on the + /// Executes the specified Func<TResult> asynchronously on the /// thread that the Dispatcher was created on. /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<TResult> delegate to invoke through the dispatcher. /// /// /// An operation representing the queued delegate to be invoked. @@ -335,11 +334,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func asynchronously on the + /// Executes the specified Func<TResult> asynchronously on the /// thread that the Dispatcher was created on. /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<TResult> delegate to invoke through the dispatcher. /// /// /// The priority that determines in what order the specified @@ -355,11 +354,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func asynchronously on the + /// Executes the specified Func<TResult> asynchronously on the /// thread that the Dispatcher was created on. /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<TResult> delegate to invoke through the dispatcher. /// /// /// The priority that determines in what order the specified @@ -479,7 +478,7 @@ public partial class Dispatcher // operation has already started when the timeout expires, // we still wait for it to complete. This is different // than simply waiting on the operation with a timeout - // because we are the ones queueing the dispatcher + // because we are the ones queuing the dispatcher // operation, not the caller. We can't leave the operation // in a state that it might execute if we return that it did not // invoke. @@ -492,12 +491,12 @@ public partial class Dispatcher // Old async semantics return from Wait without // throwing an exception if the operation was aborted. - // There is no need to test the timout condition, since + // There is no need to test the timeout condition, since // the old async semantics would just return the result, // which would be null. // This should not block because either the operation - // is using the old async sematics, or the operation + // is using the old async semantics, or the operation // completed successfully. result = operation.GetResult(); } @@ -543,11 +542,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func asynchronously on the + /// Executes the specified Func<Task> asynchronously on the /// thread that the Dispatcher was created on /// /// - /// A Func delegate to invoke through the dispatcher. + /// A Func<Task> delegate to invoke through the dispatcher. /// /// /// The priority that determines in what order the specified @@ -564,11 +563,11 @@ public partial class Dispatcher } /// - /// Executes the specified Func> asynchronously on the + /// Executes the specified Func<Task<TResult>> asynchronously on the /// thread that the Dispatcher was created on /// - /// - /// A Func> delegate to invoke through the dispatcher. + /// + /// A Func<Task<TResult>> delegate to invoke through the dispatcher. /// /// /// The priority that determines in what order the specified @@ -595,4 +594,4 @@ public partial class Dispatcher _ = action ?? throw new ArgumentNullException(nameof(action)); InvokeAsyncImpl(new SendOrPostCallbackDispatcherOperation(this, priority, action, arg, true), CancellationToken.None); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 79cc760fc6..8717b5340a 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -329,6 +329,7 @@ namespace Avalonia /// /// Gets the control's parent visual. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "GetVisualParent extension method is supposed to be used instead.")] internal Visual? VisualParent => _visualParent; /// diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index be3d5424fb..e907fd5988 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -94,6 +94,8 @@ namespace Avalonia } /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031", Justification = "This property is supposed to be a styled readonly property.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030", Justification = "False positive.")] public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty); /// diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index e10cc1d100..20711eecbc 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -2042,6 +2042,8 @@ namespace Avalonia.Controls /// /// Identifies the Value dependency property. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002:AvaloniaProperty objects should not be owned by a generic type", + Justification = "This property is not supposed to be used from XAML.")] public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register, T>(nameof(Value)); diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs index c17f5a19ab..fa956a79fe 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls { /// /// Specifies how text in the text box portion of the - /// control is used to filter items specified by the + /// control is used to filter items specified by the /// property for display in the drop-down. /// public enum AutoCompleteFilterMode diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index 2eb3ae3010..b28faba863 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -46,8 +46,8 @@ namespace Avalonia.Controls /// The width of the column. /// The width unit of the column. public ColumnDefinition(double value, GridUnitType type) + : this(new GridLength(value, type)) { - Width = new GridLength(value, type); } /// diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index fc4b11bd4a..63e28ea14d 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -56,6 +56,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1013", + Justification = "We keep PlacementModeProperty for backward compatibility.")] public static readonly StyledProperty PlacementProperty = Popup.PlacementProperty.AddOwner(); diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index eb587fb157..d0752b8aa6 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls { // start with getting SharedSizeGroup value. // this property is NOT inherited which should result in better overall perf. - if (SharedSizeGroup is { } sharedSizeGroupId && PrivateSharedSizeScope is { } privateSharedSizeScope) + if (SharedSizeGroup is { } sharedSizeGroupId && GetValue(PrivateSharedSizeScopeProperty) is { } privateSharedSizeScope) { _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); _sharedState.AddMember(this); @@ -333,7 +333,7 @@ namespace Avalonia.Controls if (definition._sharedState == null && sharedSizeGroupId != null - && definition.PrivateSharedSizeScope is { } privateSharedSizeScope) + && definition.GetValue(PrivateSharedSizeScopeProperty) is { } privateSharedSizeScope) { // if definition is not registered and both: shared size group id AND private shared scope // are available, then register definition. @@ -412,14 +412,6 @@ namespace Avalonia.Controls } } - /// - /// Private getter of shared state collection dynamic property. - /// - private SharedSizeScope? PrivateSharedSizeScope - { - get { return GetValue(PrivateSharedSizeScopeProperty); } - } - /// /// Convenience accessor to UseSharedMinimum flag /// diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index d3565cbdd5..7931ecbbde 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -18,6 +18,8 @@ namespace Avalonia.Controls.Documents AvaloniaProperty.Register( nameof(Inlines)); + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", + Justification = "Collection properties shouldn't be set with SetCurrentValue.")] public Span() { Inlines = new InlineCollection diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs index 9ed4737c7c..5b23b5030f 100644 --- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs @@ -44,7 +44,7 @@ namespace Avalonia.Controls.Primitives /// Defines the property /// public static readonly StyledProperty OverlayInputPassThroughElementProperty = - Popup.OverlayInputPassThroughElementProperty.AddOwner(); + Popup.OverlayInputPassThroughElementProperty.AddOwner(); private readonly Lazy _popupLazy; private Rect? _enlargedPopupRect; diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index f747e278f0..06069a897e 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -63,7 +63,7 @@ namespace Avalonia.Controls { if (TransformRoot == null || LayoutTransform == null) { - LayoutTransform = RenderTransform; + SetCurrentValue(LayoutTransformProperty, RenderTransform); return base.ArrangeOverride(finalSize); } @@ -176,7 +176,7 @@ namespace Avalonia.Controls else { _renderTransformChangedEvent?.Dispose(); - LayoutTransform = null; + ClearValue(LayoutTransformProperty); } } } diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index e665c2db90..e5f0b50555 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -29,18 +29,24 @@ namespace Avalonia.Controls /// /// Defines the property. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010", + Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")] public static readonly new DirectProperty SelectedItemsProperty = SelectingItemsControl.SelectedItemsProperty; /// /// Defines the property. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010", + Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")] public static readonly new DirectProperty SelectionProperty = SelectingItemsControl.SelectionProperty; /// /// Defines the property. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010", + Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")] public static readonly new StyledProperty SelectionModeProperty = SelectingItemsControl.SelectionModeProperty; @@ -84,6 +90,8 @@ namespace Avalonia.Controls /// Note that the selection mode only applies to selections made via user interaction. /// Multiple selections can be made programmatically regardless of the value of this property. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", + Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")] public new SelectionMode SelectionMode { get { return base.SelectionMode; } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index a0dbf33a1d..72febcfedb 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -53,12 +53,6 @@ namespace Avalonia.Controls public static readonly StyledProperty InputGestureProperty = AvaloniaProperty.Register(nameof(InputGesture)); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsSelectedProperty = - SelectingItemsControl.IsSelectedProperty.AddOwner(); - /// /// Defines the property. /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 329a0fa6ab..736c338c10 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -1,5 +1,5 @@ using System; - +using Avalonia.Collections; using Avalonia.Controls.Documents; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; @@ -442,7 +442,7 @@ namespace Avalonia.Controls.Presenters var contentTemplate = ContentTemplate; var oldChild = Child; var newChild = CreateChild(content, oldChild, contentTemplate); - var logicalChildren = Host?.LogicalChildren ?? LogicalChildren; + var logicalChildren = GetEffectiveLogicalChildren(); // Remove the old child if we're not recycling it. if (newChild != oldChild) @@ -488,6 +488,9 @@ namespace Avalonia.Controls.Presenters } + private IAvaloniaList GetEffectiveLogicalChildren() + => Host?.LogicalChildren ?? LogicalChildren; + /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { @@ -692,7 +695,7 @@ namespace Avalonia.Controls.Presenters else if (Child != null) { VisualChildren.Remove(Child); - LogicalChildren.Remove(Child); + GetEffectiveLogicalChildren().Remove(Child); ((ISetInheritanceParent)Child).SetParent(Child.Parent); Child = null; _recyclingDataTemplate = null; diff --git a/src/Avalonia.Controls/Primitives/UniformGrid.cs b/src/Avalonia.Controls/Primitives/UniformGrid.cs index 09554412db..fea35d867a 100644 --- a/src/Avalonia.Controls/Primitives/UniformGrid.cs +++ b/src/Avalonia.Controls/Primitives/UniformGrid.cs @@ -123,7 +123,7 @@ namespace Avalonia.Controls.Primitives if (FirstColumn >= Columns) { - FirstColumn = 0; + SetCurrentValue(FirstColumnProperty, 0); } var itemCount = FirstColumn; diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 351ed5d716..35676474dd 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -29,6 +29,9 @@ namespace Avalonia.Controls.Primitives } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031", + Justification = "A hack to make ChromeOverlayLayer lazily creatable. It is expected that GetValue(ChromeOverlayLayerProperty) alone won't work.")] public ChromeOverlayLayer ChromeOverlayLayer { get diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index daf6be12d2..7dcdaa2f8d 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -17,7 +17,13 @@ namespace Avalonia.Controls [PseudoClasses(":vertical", ":horizontal", ":indeterminate")] public class ProgressBar : RangeBase { - public class ProgressBarTemplateProperties : AvaloniaObject + /// + /// Provides calculated values for use with the 's control theme or template. + /// + /// + /// This class is NOT intended for general use outside of control templates. + /// + public class ProgressBarTemplateSettings : AvaloniaObject { private double _container2Width; private double _containerWidth; @@ -26,38 +32,38 @@ namespace Avalonia.Controls private double _container2AnimationStartPosition; private double _container2AnimationEndPosition; - public static readonly DirectProperty ContainerAnimationStartPositionProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty ContainerAnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( nameof(ContainerAnimationStartPosition), p => p.ContainerAnimationStartPosition, (p, o) => p.ContainerAnimationStartPosition = o, 0d); - public static readonly DirectProperty ContainerAnimationEndPositionProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty ContainerAnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( nameof(ContainerAnimationEndPosition), p => p.ContainerAnimationEndPosition, (p, o) => p.ContainerAnimationEndPosition = o, 0d); - public static readonly DirectProperty Container2AnimationStartPositionProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty Container2AnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( nameof(Container2AnimationStartPosition), p => p.Container2AnimationStartPosition, (p, o) => p.Container2AnimationStartPosition = o, 0d); - public static readonly DirectProperty Container2AnimationEndPositionProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty Container2AnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( nameof(Container2AnimationEndPosition), p => p.Container2AnimationEndPosition, (p, o) => p.Container2AnimationEndPosition = o); - public static readonly DirectProperty Container2WidthProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty Container2WidthProperty = + AvaloniaProperty.RegisterDirect( nameof(Container2Width), p => p.Container2Width, (p, o) => p.Container2Width = o); - public static readonly DirectProperty ContainerWidthProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty ContainerWidthProperty = + AvaloniaProperty.RegisterDirect( nameof(ContainerWidth), p => p.ContainerWidth, (p, o) => p.ContainerWidth = o); @@ -103,29 +109,57 @@ namespace Avalonia.Controls private Border? _indicator; private IDisposable? _trackSizeChangedListener; + /// + /// Defines the property. + /// public static readonly StyledProperty IsIndeterminateProperty = AvaloniaProperty.Register(nameof(IsIndeterminate)); + /// + /// Defines the property. + /// public static readonly StyledProperty ShowProgressTextProperty = AvaloniaProperty.Register(nameof(ShowProgressText)); + /// + /// Defines the property. + /// public static readonly StyledProperty ProgressTextFormatProperty = AvaloniaProperty.Register(nameof(ProgressTextFormat), "{1:0}%"); + /// + /// Defines the property. + /// public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); + /// + /// Defines the property. + /// public static readonly DirectProperty PercentageProperty = AvaloniaProperty.RegisterDirect( nameof(Percentage), o => o.Percentage); + /// + /// Defines the property. + /// public static readonly StyledProperty IndeterminateStartingOffsetProperty = AvaloniaProperty.Register(nameof(IndeterminateStartingOffset)); + /// + /// Defines the property. + /// public static readonly StyledProperty IndeterminateEndingOffsetProperty = AvaloniaProperty.Register(nameof(IndeterminateEndingOffset)); + /// + /// Gets the overall percentage complete of the progress + /// + /// + /// This read-only property is automatically calculated using the current and + /// the effective range ( - ). + /// public double Percentage { get { return _percentage; } @@ -154,31 +188,50 @@ namespace Avalonia.Controls OrientationProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); } + /// + /// Initializes a new instance of the class. + /// public ProgressBar() { UpdatePseudoClasses(IsIndeterminate, Orientation); } - public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties(); + /// + /// Gets or sets the TemplateSettings for the . + /// + public ProgressBarTemplateSettings TemplateSettings { get; } = new ProgressBarTemplateSettings(); + /// + /// Gets or sets a value indicating whether the progress bar shows the actual value or a generic, + /// continues progress indicator (indeterminate state). + /// public bool IsIndeterminate { get => GetValue(IsIndeterminateProperty); set => SetValue(IsIndeterminateProperty, value); } + /// + /// Gets or sets a value indicating whether progress text will be shown. + /// public bool ShowProgressText { get => GetValue(ShowProgressTextProperty); set => SetValue(ShowProgressTextProperty, value); } + /// + /// Gets or sets the format string applied to the internally calculated progress text before it is shown. + /// public string ProgressTextFormat { get => GetValue(ProgressTextFormatProperty); set => SetValue(ProgressTextFormatProperty, value); } + /// + /// Gets or sets the orientation of the . + /// public Orientation Orientation { get => GetValue(OrientationProperty); @@ -193,6 +246,7 @@ namespace Avalonia.Controls return result; } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -242,15 +296,14 @@ namespace Avalonia.Controls var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar - TemplateProperties.ContainerWidth = barIndicatorWidth; - TemplateProperties.Container2Width = barIndicatorWidth2; - - TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180% - TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300% + TemplateSettings.ContainerWidth = barIndicatorWidth; + TemplateSettings.Container2Width = barIndicatorWidth2; - TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150% - TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% + TemplateSettings.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180% + TemplateSettings.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300% + TemplateSettings.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150% + TemplateSettings.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% // Remove these properties when we switch to fluent as default and removed the old one. SetCurrentValue(IndeterminateStartingOffsetProperty,-dim); diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs index 530f28fbb6..ed419ad89b 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs @@ -71,6 +71,7 @@ namespace Avalonia.Controls /// /// Gets or sets a value that indicates the refresh state of the visualizer. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "False positive")] protected RefreshVisualizerState RefreshVisualizerState { get diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index fac795035b..8aaaff8cc9 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -46,8 +46,8 @@ namespace Avalonia.Controls /// The height of the row. /// The height unit of the column. public RowDefinition(double value, GridUnitType type) + : this(new GridLength(value, type)) { - Height = new GridLength(value, type); } /// @@ -56,7 +56,7 @@ namespace Avalonia.Controls /// The height of the column. public RowDefinition(GridLength height) { - Height = height; + SetCurrentValue(HeightProperty, height); } /// diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index 22f9c0832a..60d5358c49 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Avalonia.Platform; -using Avalonia.VisualTree; #nullable enable @@ -38,21 +37,58 @@ namespace Avalonia.Controls _iScreenImpl = iScreenImpl; } + /// + /// Retrieves a Screen for the display that contains the rectangle. + /// + /// Bounds that specifies the area for which to retrieve the display. + /// The . public Screen? ScreenFromBounds(PixelRect bounds) { return _iScreenImpl.ScreenFromRect(bounds); } + /// + /// Retrieves a Screen for the display that contains the specified . + /// + /// The window for which to retrieve the Screen. + /// Window platform implementation was already disposed. + /// The . + public Screen? ScreenFromWindow(WindowBase window) + { + if (window.PlatformImpl is null) + { + throw new ObjectDisposedException("Window platform implementation was already disposed."); + } + + return _iScreenImpl.ScreenFromWindow(window.PlatformImpl); + } + + /// + /// Retrieves a Screen for the display that contains the specified . + /// + /// The window impl for which to retrieve the Screen. + /// The . + [Obsolete("Use ScreenFromWindow(WindowBase) overload.")] public Screen? ScreenFromWindow(IWindowBaseImpl window) { return _iScreenImpl.ScreenFromWindow(window); } + /// + /// Retrieves a Screen for the display that contains the specified point. + /// + /// A Point that specifies the location for which to retrieve a Screen. + /// The . public Screen? ScreenFromPoint(PixelPoint point) { return _iScreenImpl.ScreenFromPoint(point); } + /// + /// Retrieves a Screen for the display that contains the specified . + /// + /// A Visual for which to retrieve a Screen. + /// The . public Screen? ScreenFromVisual(Visual visual) { var tl = visual.PointToScreen(visual.Bounds.TopLeft); diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index 8060ca9594..3ad656ee3c 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -217,8 +217,8 @@ namespace Avalonia.Controls /// /// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled /// When enabled, and the pane is open in Overlay or CompactOverlay mode, - /// the contents of the splitview are darkened to visually separate the open pane - /// and the rest of the SplitView + /// the contents of the are darkened to visually separate the open pane + /// and the rest of the . /// public bool UseLightDismissOverlayMode { @@ -227,7 +227,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the TemplateSettings for the SplitView + /// Gets or sets the TemplateSettings for the . /// public SplitViewTemplateSettings TemplateSettings { diff --git a/src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs b/src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs index f2cbf55986..1794b9260f 100644 --- a/src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs +++ b/src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs @@ -2,8 +2,10 @@ { /// /// Provides calculated values for use with the 's control theme or template. - /// This class is NOT intended for general use. /// + /// + /// This class is NOT intended for general use outside of control templates. + /// public class SplitViewTemplateSettings : AvaloniaObject { internal SplitViewTemplateSettings() { } @@ -17,12 +19,14 @@ AvaloniaProperty.Register( nameof(PaneColumnGridLength)); + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")] public double ClosedPaneWidth { get => GetValue(ClosedPaneWidthProperty); internal set => SetValue(ClosedPaneWidthProperty, value); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")] public GridLength PaneColumnGridLength { get => GetValue(PaneColumnGridLengthProperty); diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 74cf54beb8..310dd34382 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -115,6 +115,7 @@ namespace Avalonia.Controls /// /// The content of the selected tab. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")] public object? SelectedContent { get { return GetValue(SelectedContentProperty); } @@ -127,6 +128,7 @@ namespace Avalonia.Controls /// /// The content template of the selected tab. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")] public IDataTemplate? SelectedContentTemplate { get { return GetValue(SelectedContentTemplateProperty); } diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 46265fb5bc..4068404952 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -42,6 +42,8 @@ namespace Avalonia.Controls /// /// The tab strip placement. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031", + Justification = "This property is supposed to be inherited only and settable on parent TabControl.")] public Dock TabStripPlacement { get { return GetValue(TabStripPlacementProperty); } @@ -83,7 +85,7 @@ namespace Avalonia.Controls { Header = obj.NewValue; } - } + } } } } diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index a28e9791f6..108deba257 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/src/Avalonia.Controls/ToggleSwitch.cs @@ -201,7 +201,7 @@ namespace Avalonia.Controls } else { - IsChecked = shouldBecomeChecked; + SetCurrentValue(IsCheckedProperty, shouldBecomeChecked); } } else diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 881474c761..85a35a3489 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -83,11 +83,11 @@ namespace Avalonia.Controls /// public static readonly StyledProperty ActualThemeVariantProperty = - ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); + ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); /// public static readonly StyledProperty RequestedThemeVariantProperty = - ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); + ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); /// /// Defines the SystemBarColor attached property. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 48edb81b16..66cce89b9d 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -713,7 +713,7 @@ namespace Avalonia.Controls Owner = owner; owner?.AddChild(this, false); - SetWindowStartupLocation(owner?.PlatformImpl); + SetWindowStartupLocation(owner); PlatformImpl?.Show(ShowActivated, false); Renderer.Start(); @@ -789,7 +789,7 @@ namespace Avalonia.Controls Owner = owner; owner.AddChild(this, true); - SetWindowStartupLocation(owner.PlatformImpl); + SetWindowStartupLocation(owner); PlatformImpl?.Show(ShowActivated, true); @@ -870,7 +870,7 @@ namespace Avalonia.Controls } } - private void SetWindowStartupLocation(IWindowBaseImpl? owner = null) + private void SetWindowStartupLocation(Window? owner = null) { var startupLocation = WindowStartupLocation; diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 5bc9909ef3..fb0504ba1e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -37,7 +37,7 @@ namespace Avalonia.Diagnostics.Controls _ => null }; - RequestedThemeVariant = application.RequestedThemeVariant; + SetCurrentValue(RequestedThemeVariantProperty, application.RequestedThemeVariant); _application.PropertyChanged += ApplicationOnPropertyChanged; } @@ -131,7 +131,7 @@ namespace Avalonia.Diagnostics.Controls { if (e.Property == Avalonia.Application.RequestedThemeVariantProperty) { - RequestedThemeVariant = e.GetNewValue(); + SetCurrentValue(RequestedThemeVariantProperty, e.GetNewValue()); } } diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 22ee548823..4cae8e82df 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -12,4 +12,5 @@ + diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 21cdef2634..b0978dc3f6 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -17,6 +17,7 @@ namespace Avalonia.ReactiveUI /// ViewModel type. public class ReactiveUserControl : UserControl, IViewFor where TViewModel : class { + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002", Justification = "Generic avalonia property is expected here.")] public static readonly StyledProperty ViewModelProperty = AvaloniaProperty .Register, TViewModel?>(nameof(ViewModel)); diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs index 726fb3d661..14e9353096 100644 --- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs +++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs @@ -17,6 +17,7 @@ namespace Avalonia.ReactiveUI /// ViewModel type. public class ReactiveWindow : Window, IViewFor where TViewModel : class { + [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002", Justification = "Generic avalonia property is expected here.")] public static readonly StyledProperty ViewModelProperty = AvaloniaProperty .Register, TViewModel?>(nameof(ViewModel)); diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index 8d266ce82f..e63a0c9c26 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -15,4 +15,5 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml index 6004d42120..2113ae4cb0 100644 --- a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml @@ -1,18 +1,21 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:generic="using:System.Collections.Generic"> - - Alabama - Alaska - Arizona - Arkansas - California - Colorado - Connecticut - Delaware - + + + Alabama + Alaska + Arizona + Arkansas + California + Colorado + Connecticut + Delaware + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index 253d85852e..64a598e359 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -125,13 +125,13 @@ - + - + - + @@ -140,13 +140,13 @@ - + - + - + @@ -155,13 +155,13 @@ - + - + - + @@ -170,28 +170,28 @@ - + - + - + diff --git a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj index 4aa6b66743..03193599ed 100644 --- a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj +++ b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj @@ -15,4 +15,5 @@ + diff --git a/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml index 3eb158d5b6..1f4bf006a0 100644 --- a/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml @@ -93,7 +93,7 @@ - + diff --git a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs index df1a24fa0f..993414f17f 100644 --- a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs +++ b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs @@ -37,7 +37,7 @@ namespace Avalonia.Browser return false; } - public void Render(IDrawingContextImpl context) + public void Render(ImmediateDrawingContext context) { _hasRendered = true; _onFirstRender(); diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index b23697fd2a..38375045cb 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -433,12 +433,7 @@ namespace Avalonia.Headless { } - - public void Custom(ICustomDrawOperation custom) - { - - } - + public object? GetFeature(Type t) { return null; diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs index 8fbc5ec6ef..7d4b7f5477 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs @@ -17,7 +17,7 @@ public static class HeadlessWindowExtensions /// Triggers a renderer timer tick and captures last rendered frame. /// /// Bitmap with last rendered frame. Null, if nothing was rendered. - public static Bitmap? CaptureRenderedFrame(this TopLevel topLevel) + public static WriteableBitmap? CaptureRenderedFrame(this TopLevel topLevel) { Dispatcher.UIThread.RunJobs(); AvaloniaHeadlessPlatform.ForceRenderTimerTick(); @@ -29,7 +29,7 @@ public static class HeadlessWindowExtensions /// Note, in order to trigger rendering timer, call method. /// /// Bitmap with last rendered frame. Null, if nothing was rendered. - public static Bitmap? GetLastRenderedFrame(this TopLevel topLevel) + public static WriteableBitmap? GetLastRenderedFrame(this TopLevel topLevel) { if (AvaloniaLocator.Current.GetService() is HeadlessPlatformRenderInterface) { diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index b15c1eb327..93f92d46f8 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -214,7 +214,7 @@ namespace Avalonia.Headless }); } - public Bitmap? GetLastRenderedFrame() + public WriteableBitmap? GetLastRenderedFrame() { lock (_sync) { @@ -224,7 +224,7 @@ namespace Avalonia.Headless } using var lockedFramebuffer = _lastRenderedFrame.Lock(); - return new Bitmap(lockedFramebuffer.Format, AlphaFormat.Opaque, lockedFramebuffer.Address, + return new WriteableBitmap(lockedFramebuffer.Format, AlphaFormat.Opaque, lockedFramebuffer.Address, lockedFramebuffer.Size, lockedFramebuffer.Dpi, lockedFramebuffer.RowBytes); } } diff --git a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs index f3da2335bc..ef501b3800 100644 --- a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs +++ b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs @@ -8,7 +8,7 @@ namespace Avalonia.Headless { internal interface IHeadlessWindow { - Bitmap? GetLastRenderedFrame(); + WriteableBitmap? GetLastRenderedFrame(); void KeyPress(Key key, RawInputModifiers modifiers); void KeyRelease(Key key, RawInputModifiers modifiers); void MouseDown(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 6c7b04dbfb..819e721b36 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -155,7 +155,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions result = new XamlStaticOrTargetedReturnMethodCallNode(node, type.GetMethod( new FindMethodMethodSignature("FromUInt32", type, types.UInt) { IsStatic = true }), - new[] { new XamlConstantNode(node, types.UInt, color.ToUint32()) }); + new[] { new XamlConstantNode(node, types.UInt, color.ToUInt32()) }); return true; } @@ -242,7 +242,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions result = new XamlAstNewClrObjectNode(node, brushTypeRef, types.ImmutableSolidColorBrushConstructorColor, - new List { new XamlConstantNode(node, types.UInt, color.ToUint32()) }); + new List { new XamlConstantNode(node, types.UInt, color.ToUInt32()) }); return true; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index 0cc7cc5468..f8eab5b654 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -50,6 +50,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime private readonly IServiceProvider? _parentProvider; private readonly List? _parentResourceNodes; private readonly INameScope _nameScope; + private IRuntimePlatform? _runtimePlatform; public DeferredParentServiceProvider(IServiceProvider? parentProvider, List? parentResourceNodes, object rootObject, INameScope nameScope) @@ -80,6 +81,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime return this; if (serviceType == typeof(IAvaloniaXamlIlControlTemplateProvider)) return this; + if (serviceType == typeof(IRuntimePlatform)) + { + if(_runtimePlatform == null) + _runtimePlatform = AvaloniaLocator.Current.GetService(); + return _runtimePlatform; + } return _parentProvider?.GetService(serviceType); } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index f48d45f961..cea55f4d29 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -5,12 +5,9 @@ using System.Linq; using System.Threading; using Avalonia.Media; using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Media.Imaging; -using Avalonia.Skia.Helpers; using SkiaSharp; using ISceneBrush = Avalonia.Media.ISceneBrush; @@ -667,12 +664,6 @@ namespace Avalonia.Skia _currentBlendingMode = _blendingModeStack.Pop(); } - public void Custom(ICustomDrawOperation custom) - { - CheckLease(); - custom.Render(this); - } - /// public void PushOpacityMask(IBrush mask, Rect bounds) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 318b0fe9ae..be5cef35b5 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Numerics; using Avalonia.Media; using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Avalonia.Media.Imaging; using SharpDX; @@ -608,8 +606,7 @@ namespace Avalonia.Direct2D1.Media { PopLayer(); } - - public void Custom(ICustomDrawOperation custom) => custom.Render(this); + public object GetFeature(Type t) => null; } } diff --git a/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj b/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj new file mode 100644 index 0000000000..c27801db61 --- /dev/null +++ b/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj @@ -0,0 +1,22 @@ + + + netstandard2.0 + false + Avalonia.Analyzers + true + true + true + + + + + + + + + + + + + + diff --git a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs b/src/tools/Avalonia.Analyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs similarity index 100% rename from src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs rename to src/tools/Avalonia.Analyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs diff --git a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.cs b/src/tools/Avalonia.Analyzers/AvaloniaPropertyAnalyzer.cs similarity index 95% rename from src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.cs rename to src/tools/Avalonia.Analyzers/AvaloniaPropertyAnalyzer.cs index d1d9071d17..7de0e8eac4 100644 --- a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.cs +++ b/src/tools/Avalonia.Analyzers/AvaloniaPropertyAnalyzer.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.Serialization; using Microsoft.CodeAnalysis; @@ -14,7 +13,6 @@ using Microsoft.CodeAnalysis.Operations; namespace Avalonia.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] -[SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:Enable analyzer release tracking")] public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer { private const string Category = "AvaloniaProperty"; @@ -68,7 +66,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Type mismatch: AvaloniaProperty owner is {0}, which is not the containing type", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "The owner of an AvaloniaProperty should generally be the containing type. This ensures that the property can be used as expected in XAML.", TypeMismatchTag); @@ -78,7 +76,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Unexpected property use: {0} is neither owned by nor attached to {1}", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "It is possible to use any AvaloniaProperty with any AvaloniaObject. However, each AvaloniaProperty an object uses on itself should be either owned by that object, or attached to that object.", InappropriateReadWriteTag); @@ -88,7 +86,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Inappropriate assignment: An AvaloniaObject should use SetCurrentValue when setting its own StyledProperty or AttachedProperty values", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "The standard means of setting an AvaloniaProperty is to call the SetValue method (often via a CLR property setter). This will forcibly overwrite values from sources like styles and templates, " + "which is something that should only be done by consumers of the control, not the control itself. Controls which want to set their own values should instead call the SetCurrentValue method, or " + "refactor the property into a DirectProperty. An assignment is exempt from this diagnostic in two scenarios: when it is forwarding a constructor parameter, and when the target object is derived " + @@ -101,7 +99,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Superfluous owner: {0} is already an owner of {1} via {2}", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "Ownership of an AvaloniaProperty is inherited along the type hierarchy. There is no need for a derived type to assert ownership over a base type's properties. This diagnostic can be a symptom of an incorrect property owner elsewhere.", InappropriateReadWriteTag); @@ -111,7 +109,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Name collision: {0} has the same name as {1}", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "Querying for an AvaloniaProperty by name requires that each property associated with a type have a unique name.", NameCollisionTag); @@ -121,7 +119,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Name collision: {0} owns multiple Avalonia properties with the name '{1}' {2}", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "It is unclear which AvaloniaProperty this CLR property refers to. Ensure that each AvaloniaProperty associated with a type has a unique name. If you need to change behaviour of a base property in your class, call its OverrideMetadata or OverrideDefaultValue methods.", NameCollisionTag); @@ -131,7 +129,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Bad name: An AvaloniaProperty named '{0}' is being assigned to {1}. These names do not relate.", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "An AvaloniaProperty should be stored in a field or property which contains its name. For example, a property named \"Brush\" should be assigned to a field called \"BrushProperty\".\nPrivate symbols are exempt from this diagnostic.", NameCollisionTag); @@ -141,7 +139,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Side effects: '{0}' is an AvaloniaProperty which can be {1} without the use of this CLR property. This {2} accessor should do nothing except call {3}.", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call any user CLR properties. To execute code before or after the property is set, consider: 1) adding a Coercion method, b) adding a static observer with AvaloniaProperty.Changed.AddClassHandler, and/or c) overriding the AvaloniaObject.OnPropertyChanged method.", AssociatedClrPropertyTag); @@ -151,7 +149,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Missing accessor: {0} is {1}, but this CLR property lacks a {2} accessor", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. Not providing both CLR property accessors is ineffective.", AssociatedClrPropertyTag); @@ -161,7 +159,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Inconsistent accessibility: CLR {0} accessibility does not match accessibility of {1}", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. Defining a CLR property with different accessibility from its associated AvaloniaProperty is ineffective.", AssociatedClrPropertyTag); @@ -171,7 +169,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer "Type mismatch: CLR property type differs from the value type of {0} {1}", Category, DiagnosticSeverity.Warning, - isEnabledByDefault: true, + isEnabledByDefault: false, // TODO: autogenerate property metadata preserved in ref assembly "The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. A CLR property changing the value type (even when an implicit cast is possible) is ineffective and can lead to InvalidCastException to be thrown.", TypeMismatchTag, AssociatedClrPropertyTag); diff --git a/src/tools/Avalonia.Analyzers/GlobalSuppressions.cs b/src/tools/Avalonia.Analyzers/GlobalSuppressions.cs new file mode 100644 index 0000000000..9428b904b8 --- /dev/null +++ b/src/tools/Avalonia.Analyzers/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:Enable analyzer release tracking")] diff --git a/src/tools/Avalonia.Analyzers/OnPropertyChangedOverrideAnalyzer.cs b/src/tools/Avalonia.Analyzers/OnPropertyChangedOverrideAnalyzer.cs new file mode 100644 index 0000000000..6fbfe28bd8 --- /dev/null +++ b/src/tools/Avalonia.Analyzers/OnPropertyChangedOverrideAnalyzer.cs @@ -0,0 +1,59 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Avalonia.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class OnPropertyChangedOverrideAnalyzer : DiagnosticAnalyzer +{ + public const string DiagnosticId = "AVA2001"; + + private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId, + "Missing invoke base.OnPropertyChanged", + "Method '{0}' do not invoke base.{0}", + "Potential issue", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "The OnPropertyChanged of the base class was not invoked in the override method declaration, which could lead to unwanted behavior."); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeMethod, SyntaxKind.MethodDeclaration); + } + + private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) + { + var method = (MethodDeclarationSyntax)context.Node; + if (context.SemanticModel.GetDeclaredSymbol(method, context.CancellationToken) is IMethodSymbol currentMethod + && currentMethod.Name == "OnPropertyChanged" + && currentMethod.OverriddenMethod is IMethodSymbol originalMethod) + { + var baseInvocations = method.Body?.DescendantNodes().OfType(); + if (baseInvocations?.Any() == true) + { + foreach (var baseInvocation in baseInvocations) + { + if (baseInvocation.Parent is SyntaxNode parent) + { + var targetSymbol = context.SemanticModel.GetSymbolInfo(parent, context.CancellationToken); + if (SymbolEqualityComparer.Default.Equals(targetSymbol.Symbol, originalMethod)) + { + return; + } + } + } + } + context.ReportDiagnostic(Diagnostic.Create(Rule, currentMethod.Locations[0], currentMethod.Name)); + } + } + +} diff --git a/src/tools/Avalonia.Generators/GeneratorContextExtensions.cs b/src/tools/Avalonia.Generators/GeneratorContextExtensions.cs index d057e8732e..3e4426e6bb 100644 --- a/src/tools/Avalonia.Generators/GeneratorContextExtensions.cs +++ b/src/tools/Avalonia.Generators/GeneratorContextExtensions.cs @@ -20,7 +20,7 @@ internal static class GeneratorContextExtensions public static void ReportNameGeneratorUnhandledError(this GeneratorExecutionContext context, Exception error) => context.Report(UnhandledErrorDescriptorId, "Unhandled exception occured while generating typed Name references. " + - "Please file an issue: https://github.com/avaloniaui/Avalonia.Generators", + "Please file an issue: https://github.com/avaloniaui/Avalonia", error.ToString()); public static void ReportNameGeneratorInvalidType(this GeneratorExecutionContext context, string typeName) => diff --git a/src/tools/PublicAnalyzers/Avalonia.Analyzers.csproj b/src/tools/PublicAnalyzers/Avalonia.Analyzers.csproj deleted file mode 100644 index 31b8d08541..0000000000 --- a/src/tools/PublicAnalyzers/Avalonia.Analyzers.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netstandard2.0 - enable - true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - diff --git a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs index 894b6578e3..d840ea171e 100644 --- a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs @@ -7,8 +7,10 @@ using Xunit; namespace Avalonia.Base.UnitTests; -public class AssetLoaderTests : IDisposable +public class AssetLoaderTests { + private IAssemblyDescriptorResolver _resolver; + public class MockAssembly : Assembly { } private const string AssemblyNameWithWhitespace = "Awesome Library"; @@ -17,22 +19,20 @@ public class AssetLoaderTests : IDisposable public AssetLoaderTests() { - var resolver = Mock.Of(); + _resolver = Mock.Of(); var descriptor = CreateAssemblyDescriptor(AssemblyNameWithWhitespace); - Mock.Get(resolver).Setup(x => x.GetAssembly(AssemblyNameWithWhitespace)).Returns(descriptor); + Mock.Get(_resolver).Setup(x => x.GetAssembly(AssemblyNameWithWhitespace)).Returns(descriptor); descriptor = CreateAssemblyDescriptor(AssemblyNameWithNonAscii); - Mock.Get(resolver).Setup(x => x.GetAssembly(AssemblyNameWithNonAscii)).Returns(descriptor); - - AssetLoader.SetAssemblyDescriptorResolver(resolver); + Mock.Get(_resolver).Setup(x => x.GetAssembly(AssemblyNameWithNonAscii)).Returns(descriptor); } [Fact] public void AssemblyName_With_Whitespace_Should_Load_Resm() { var uri = new Uri($"resm:Avalonia.Base.UnitTests.Assets.something?assembly={AssemblyNameWithWhitespace}"); - var loader = new AssetLoader(); + var loader = new StandardAssetLoader(_resolver); var assemblyActual = loader.GetAssembly(uri, null); @@ -43,7 +43,7 @@ public class AssetLoaderTests : IDisposable public void AssemblyName_With_Non_ASCII_Should_Load_Avares() { var uri = new Uri($"avares://{AssemblyNameWithNonAscii}/Assets/something"); - var loader = new AssetLoader(); + var loader = new StandardAssetLoader(_resolver); var assemblyActual = loader.GetAssembly(uri, null); @@ -54,7 +54,7 @@ public class AssetLoaderTests : IDisposable public void Invalid_AssemblyName_Should_Yield_Empty_Enumerable() { var uri = new Uri($"avares://InvalidAssembly"); - var loader = new AssetLoader(); + var loader = new StandardAssetLoader(_resolver); var assemblyActual = loader.GetAssets(uri, null); @@ -71,9 +71,4 @@ public class AssetLoaderTests : IDisposable Mock.Get(descriptor).Setup(x => x.Assembly).Returns(assembly); return descriptor; } - - public void Dispose() - { - AssetLoader.SetAssemblyDescriptorResolver(new AssemblyDescriptorResolver()); - } } diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs index 09f78c5a6c..45a6efdd4a 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs @@ -514,5 +514,38 @@ namespace Avalonia.Base.UnitTests.Layout Assert.True(parent.IsMeasureValid); Assert.True(parent.IsArrangeValid); } + + [Fact] + public void Grandparent_Can_Invalidate_Root_Measure_During_Arrange() + { + // Issue #11161. + var child = new LayoutTestControl(); + var parent = new LayoutTestControl { Child = child }; + var grandparent = new LayoutTestControl { Child = parent }; + var root = new LayoutTestRoot { Child = grandparent }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + grandparent.DoArrangeOverride = (_, s) => + { + root.InvalidateMeasure(); + return s; + }; + grandparent.CallBaseArrange = true; + + child.InvalidateMeasure(); + grandparent.InvalidateMeasure(); + + root.LayoutManager.ExecuteLayoutPass(); + + Assert.True(child.IsMeasureValid); + Assert.True(child.IsArrangeValid); + Assert.True(parent.IsMeasureValid); + Assert.True(parent.IsArrangeValid); + Assert.True(grandparent.IsMeasureValid); + Assert.True(grandparent.IsArrangeValid); + Assert.True(root.IsMeasureValid); + Assert.True(root.IsArrangeValid); + } } } diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs index 62de81006e..d85c7ed9bc 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutTestControl.cs @@ -10,21 +10,41 @@ namespace Avalonia.Base.UnitTests.Layout public bool Arranged { get; set; } public Func DoMeasureOverride { get; set; } public Func DoArrangeOverride { get; set; } + public bool CallBaseMeasure { get; set; } + public bool CallBaseArrange { get; set; } protected override Size MeasureOverride(Size availableSize) { Measured = true; - return DoMeasureOverride != null ? - DoMeasureOverride(this, availableSize) : - base.MeasureOverride(availableSize); + + if (DoMeasureOverride is not null) + { + var overrideResult = DoMeasureOverride(this, availableSize); + return CallBaseMeasure ? + base.MeasureOverride(overrideResult) : + overrideResult; + } + else + { + return base.MeasureOverride(availableSize); + } } protected override Size ArrangeOverride(Size finalSize) { Arranged = true; - return DoArrangeOverride != null ? - DoArrangeOverride(this, finalSize) : - base.ArrangeOverride(finalSize); + + if (DoArrangeOverride is not null) + { + var overrideResult = DoArrangeOverride(this, finalSize); + return CallBaseArrange ? + base.ArrangeOverride(overrideResult) : + overrideResult; + } + else + { + return base.ArrangeOverride(finalSize); + } } } } diff --git a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs index b044bcde59..a32f98e462 100644 --- a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs @@ -17,7 +17,7 @@ namespace Avalonia.Benchmarks.Styling private static IDisposable CreateApp() { var services = new TestServices( - assetLoader: new AssetLoader(), + assetLoader: new StandardAssetLoader(), globalClock: new MockGlobalClock(), platform: new AppBuilder().RuntimePlatform, renderInterface: new MockPlatformRenderInterface(), diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index bece711426..d5e4693666 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -359,16 +359,21 @@ namespace Avalonia.Controls.UnitTests target.Presenter.ApplyTemplate(); Assert.Equal(target, target.Presenter.Child.GetLogicalParent()); + Assert.Equal(new[] { target.Presenter.Child }, target.LogicalChildren); root.Child = null; Assert.Null(target.Template); target.Content = null; + + Assert.Empty(target.LogicalChildren); + root.Child = target; target.Content = "Bar"; Assert.Equal(target, target.Presenter.Child.GetLogicalParent()); + Assert.Equal(new[] { target.Presenter.Child }, target.LogicalChildren); } private static FuncControlTemplate GetTemplate() diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 535b96420a..523b31e60a 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -34,7 +34,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions DelayedBinding.ApplyBindings(border); var brush = (ISolidColorBrush)border.Background; - Assert.Equal(0xff506070, brush.Color.ToUint32()); + Assert.Equal(0xff506070, brush.Color.ToUInt32()); } [Fact] @@ -81,7 +81,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions DelayedBinding.ApplyBindings(border); var brush = (ISolidColorBrush)border.Background; - Assert.Equal(0xff506070, brush.Color.ToUint32()); + Assert.Equal(0xff506070, brush.Color.ToUInt32()); } [Fact] @@ -109,7 +109,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions DelayedBinding.ApplyBindings(border); var brush = (ISolidColorBrush)border.Background; - Assert.Equal(0xff506070, brush.Color.ToUint32()); + Assert.Equal(0xff506070, brush.Color.ToUInt32()); } [Fact] @@ -141,7 +141,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions DelayedBinding.ApplyBindings(border); var brush = (ISolidColorBrush)border.Background; - Assert.Equal(0xff506070, brush.Color.ToUint32()); + Assert.Equal(0xff506070, brush.Color.ToUInt32()); } [Fact] @@ -161,7 +161,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var border = window.FindControl("border"); var brush = (SolidColorBrush)border.Background; - Assert.Equal(0xff506070, brush.Color.ToUint32()); + Assert.Equal(0xff506070, brush.Color.ToUInt32()); } } @@ -187,7 +187,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions window.Show(); var brush = (SolidColorBrush)border.Background; - Assert.Equal(0xff506070, brush.Color.ToUint32()); + Assert.Equal(0xff506070, brush.Color.ToUInt32()); } } @@ -214,7 +214,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var button = window.FindControl