From 6d49ffc95550d29f5c64f874c9163d4c86559016 Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Wed, 1 Feb 2023 17:33:16 +0100 Subject: [PATCH 01/76] Added tests for binding value types to null --- .../Data/BindingTests.cs | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 3ba8e8354d..c312a71d44 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -648,16 +648,69 @@ namespace Avalonia.Markup.UnitTests.Data }; } + [Fact] + public void Binding_Producing_Default_Value_Should_Result_In_Correct_Priority() + { + var defaultValue = StyledPropertyClass.NullableDoubleProperty.GetDefaultValue(typeof(StyledPropertyClass)); + + var vm = new NullableValuesViewModel() { NullableDouble = defaultValue }; + var target = new StyledPropertyClass(); + + target.Bind(StyledPropertyClass.NullableDoubleProperty, new Binding(nameof(NullableValuesViewModel.NullableDouble)) { Source = vm }); + + Assert.Equal(BindingPriority.LocalValue, target.GetDiagnosticInternal(StyledPropertyClass.NullableDoubleProperty).Priority); + Assert.Equal(defaultValue, target.GetValue(StyledPropertyClass.NullableDoubleProperty)); + } + + [Fact] + public void Binding_Non_Nullable_ValueType_To_Null_Reverts_To_Default_Value() + { + var source = new NullableValuesViewModel { NullableDouble = 42 }; + var target = new StyledPropertyClass(); + var binding = new Binding(nameof(source.NullableDouble)) { Source = source }; + + target.Bind(StyledPropertyClass.DoubleValueProperty, binding); + Assert.Equal(42, target.DoubleValue); + + source.NullableDouble = null; + + Assert.Equal(12.3, target.DoubleValue); + } + + [Fact] + public void Binding_Nullable_ValueType_To_Null_Sets_Value_To_Null() + { + var source = new NullableValuesViewModel { NullableDouble = 42 }; + var target = new StyledPropertyClass(); + var binding = new Binding(nameof(source.NullableDouble)) { Source = source }; + + target.Bind(StyledPropertyClass.NullableDoubleProperty, binding); + Assert.Equal(42, target.NullableDouble); + + source.NullableDouble = null; + + Assert.Null(target.NullableDouble); + } + private class StyledPropertyClass : AvaloniaObject { public static readonly StyledProperty DoubleValueProperty = - AvaloniaProperty.Register(nameof(DoubleValue)); + AvaloniaProperty.Register(nameof(DoubleValue), 12.3); public double DoubleValue { get { return GetValue(DoubleValueProperty); } set { SetValue(DoubleValueProperty, value); } } + + public static StyledProperty NullableDoubleProperty = + AvaloniaProperty.Register(nameof(NullableDoubleProperty), -1); + + public double? NullableDouble + { + get => GetValue(NullableDoubleProperty); + set => SetValue(NullableDoubleProperty, value); + } } private class DirectPropertyClass : AvaloniaObject @@ -676,6 +729,21 @@ namespace Avalonia.Markup.UnitTests.Data } } + private class NullableValuesViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + private double? _nullableDouble; + public double? NullableDouble + { + get => _nullableDouble; set + { + _nullableDouble = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NullableDouble))); + } + } + } + private class TestStackOverflowViewModel : INotifyPropertyChanged { public int SetterInvokedCount { get; private set; } From 796722f31944df2f62c2f56bffeec5ad6e39d32a Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Sat, 21 Jan 2023 16:17:10 +0100 Subject: [PATCH 02/76] Never convert null to UnsetValue in bindings Revert ToggleButton.IsChecked hack --- .../Data/Converters/DefaultValueConverter.cs | 2 +- .../Primitives/ToggleButton.cs | 2 +- .../Data/Core/BindingExpressionTests.cs | 25 ------------------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index f5c135459d..aeb71d16ae 100644 --- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs @@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters { if (value == null) { - return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null; + return null; } if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs index dfb436a55e..158c5d875b 100644 --- a/src/Avalonia.Controls/Primitives/ToggleButton.cs +++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs @@ -20,7 +20,7 @@ namespace Avalonia.Controls.Primitives nameof(IsChecked), o => o.IsChecked, (o, v) => o.IsChecked = v, - unsetValue: null, + unsetValue: false, defaultBindingMode: BindingMode.TwoWay); /// diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs index 339cf8a334..924e844ec5 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs @@ -78,18 +78,6 @@ namespace Avalonia.Base.UnitTests.Data.Core GC.KeepAlive(data); } - [Fact] - public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue() - { - var data = new Class1 { StringValue = null }; - var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double)); - var result = await target.Take(1); - - Assert.Equal(AvaloniaProperty.UnsetValue, result); - - GC.KeepAlive(data); - } - [Fact] public void Should_Convert_Set_String_To_Double() { @@ -249,19 +237,6 @@ namespace Avalonia.Base.UnitTests.Data.Core GC.KeepAlive(data); } - [Fact] - public void Should_Coerce_Setting_Null_Double_To_Default_Value() - { - var data = new Class1 { DoubleValue = 5.6 }; - var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string)); - - target.OnNext(null); - - Assert.Equal(0, data.DoubleValue); - - GC.KeepAlive(data); - } - [Fact] public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value() { From f2c3805d6a0086ce27ac9ecb55680def9f31b9ea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Feb 2023 18:08:13 +0100 Subject: [PATCH 03/76] Update ncrunch config. --- .ncrunch/Avalonia.UnitTests.v3.ncrunchproject | 5 +++++ .ncrunch/GpuInterop.v3.ncrunchproject | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .ncrunch/Avalonia.UnitTests.v3.ncrunchproject create mode 100644 .ncrunch/GpuInterop.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..cff5044edf --- /dev/null +++ b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/GpuInterop.v3.ncrunchproject b/.ncrunch/GpuInterop.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/GpuInterop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From 1e926dac72b4c666d4c213cd72693d4cbf551e27 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Feb 2023 18:19:40 +0100 Subject: [PATCH 04/76] Added failing tests for #10110. --- .../AvaloniaObjectTests_Binding.cs | 18 +++++++++++++ .../Xaml/ResourceDictionaryTests.cs | 25 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 030d6ba215..f0386615c5 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -1285,6 +1285,24 @@ namespace Avalonia.Base.UnitTests subscription.Dispose(); } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Binding_Producing_UnsetValue_Does_Not_Cause_Unsubscribe(BindingPriority priority) + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.FooProperty, source, priority); + + source.OnNext("foo"); + Assert.Equal("foo", target.GetValue(Class1.FooProperty)); + source.OnNext(BindingValue.Unset); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); + source.OnNext("bar"); + Assert.Equal("bar", target.GetValue(Class1.FooProperty)); + } + [Fact] public void Produces_Correct_Values_And_Base_Values_With_Multiple_Animation_Bindings() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 6cab83751f..bea6bc4dc8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -307,7 +307,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(buttonResources.ContainsDeferredKey("Red")); } } - + + [Fact] + public void Dynamically_Changing_Referenced_Resources_Works_With_DynamicResource() + { + var xaml = @" + + + Red + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.Equal(Colors.Red, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + + userControl.Resources.Remove("color"); + Assert.Equal(default, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + + userControl.Resources.Add("color", Colors.Blue); + Assert.Equal(Colors.Blue, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From 7b00ef69892358650c10e0f27fb0d5dd1477e3f5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 12:00:40 +0100 Subject: [PATCH 05/76] Make untyped bindings produce default value. This changes the behavior of bindings slightly, in that previously a binding which produced `UnsetValue` (or a binding error) caused the value of any lower priority value of value to take effect. After this change, the binding reverts to the property's default value. This behavior more closely matches WPF and fixes #10110. --- src/Avalonia.Base/AvaloniaObject.cs | 4 +- .../PropertyStore/BindingEntryBase.cs | 47 +++++++++---------- .../LocalValueBindingObserver.cs | 42 ++++++++++++----- .../LocalValueUntypedBindingObserver.cs | 25 +++++++--- .../SourceUntypedBindingEntry.cs | 2 + .../PropertyStore/TypedBindingEntry.cs | 2 + .../PropertyStore/UntypedBindingEntry.cs | 5 ++ .../AvaloniaObjectTests_Binding.cs | 18 ++++++- .../AvaloniaObjectTests_Validation.cs | 4 +- 9 files changed, 99 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1946d4ba5c..db6dbe98e0 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -270,8 +270,8 @@ namespace Avalonia /// The property. /// True if the property is set, otherwise false. /// - /// Checks whether a value is assigned to the property, or that there is a binding to the - /// property that is producing a value other than . + /// Returns true if is a styled property which has a value + /// assigned to it or a binding targeting it; otherwise false. /// public bool IsSet(AvaloniaProperty property) { diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 11dc80ef8f..e1ff0970c2 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore private IDisposable? _subscription; private bool _hasValue; private TValue? _value; + private TValue? _defaultValue; + private bool _isDefaultValueInitialized; protected BindingEntryBase( ValueFrame frame, @@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore protected abstract BindingValue ConvertAndValidate(TSource value); protected abstract BindingValue ConvertAndValidate(BindingValue value); + protected abstract TValue GetDefaultValue(Type ownerType); protected virtual void Start(bool produceValue) { @@ -104,17 +107,6 @@ namespace Avalonia.PropertyStore }; } - private void ClearValue() - { - if (_hasValue) - { - _hasValue = false; - _value = default; - if (_subscription is not null) - Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority); - } - } - private void SetValue(BindingValue value) { static void Execute(BindingEntryBase instance, BindingValue value) @@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value); - if (value.HasValue) - { - if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, value.Value)) - { - instance._value = value.Value; - instance._hasValue = true; - if (instance._subscription is not null && instance._subscription != s_creatingQuiet) - instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); - } - } - else if (value.Type != BindingValueType.DoNothing) + var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); + + if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, effectiveValue)) { - instance.ClearValue(); + instance._value = effectiveValue; + instance._hasValue = true; if (instance._subscription is not null && instance._subscription != s_creatingQuiet) - instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority); + instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); } } + if (value.Type == BindingValueType.DoNothing) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore _subscription = null; Frame.OnBindingCompleted(this); } + + private TValue GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index f89cb029b6..0771ade647 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueBindingObserver(ValueStore owner, StyledProperty property) { @@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore public void OnNext(T value) { - static void Execute(ValueStore owner, StyledProperty property, T value) + static void Execute(LocalValueBindingObserver instance, T value) { - if (property.ValidateValue?.Invoke(value) != false) - owner.SetValue(property, value, BindingPriority.LocalValue); - else - owner.ClearLocalValue(property); + var owner = instance._owner; + var property = instance.Property; + + if (property.ValidateValue?.Invoke(value) == false) + value = instance.GetCachedDefaultValue(); + + owner.SetValue(property, value, BindingPriority.LocalValue); } if (Dispatcher.UIThread.CheckAccess()) { - Execute(_owner, Property, value); + Execute(this, value); } else { // To avoid allocating closure in the outer scope we need to capture variables // locally. This allows us to skip most of the allocations when on UI thread. - var instance = _owner; - var property = Property; + var instance = this; var newValue = value; - Dispatcher.UIThread.Post(() => Execute(instance, property, newValue)); + Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } @@ -73,12 +77,13 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(owner.Owner, property, value); - if (value.HasValue) - owner.SetValue(property, value.Value, BindingPriority.LocalValue); - else if (value.Type != BindingValueType.DataValidationError) - owner.ClearLocalValue(property); + var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); + owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); } + if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -92,5 +97,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs index 2d157b2519..46e6ed810a 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Cryptography; using Avalonia.Data; using Avalonia.Threading; @@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty property) { @@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore if (value == AvaloniaProperty.UnsetValue) { - owner.ClearLocalValue(property); - } - else if (value == BindingOperations.DoNothing) - { - // Do nothing! + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); } else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue)) { @@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore } else { - owner.ClearLocalValue(property); + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value); } } + if (value == BindingOperations.DoNothing) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs index b56d0d4529..b82714817b 100644 --- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs @@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs index 697725c87b..550f5c0001 100644 --- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs @@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore return value; } + + protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs index f8becb2e06..a77d7fddb6 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs @@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override object? GetDefaultValue(Type ownerType) + { + return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue; + } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index f0386615c5..baaed5104d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -372,6 +372,20 @@ namespace Avalonia.Base.UnitTests Assert.Null(target.GetValue(property)); } + [Fact] + public void LocalValue_Bind_Generic_To_ValueType_Accepts_UnsetValue() + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.QuxProperty, source); + source.OnNext(6.7); + source.OnNext(BindingValue.Unset); + + Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); + } + [Fact] public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue() { @@ -383,7 +397,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] @@ -397,7 +411,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index e8175cf477..3fc950573d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -66,7 +66,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Reverts_To_Lower_Priority_If_Style_Binding_Fails_Validation() + public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_2() { var target = new Class1(); var source = new Subject(); @@ -75,7 +75,7 @@ namespace Avalonia.Base.UnitTests target.Bind(Class1.FooProperty, source, BindingPriority.StyleTrigger); source.OnNext(150); - Assert.Equal(10, target.GetValue(Class1.FooProperty)); + Assert.Equal(11, target.GetValue(Class1.FooProperty)); } [Fact] From bca57ea0e9c65c5b17422303261b1a7a058d842b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 16:27:48 +0100 Subject: [PATCH 06/76] Added additional failing test. --- .../AvaloniaObjectTests_Validation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index 3fc950573d..513aeb65ab 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -78,6 +78,20 @@ namespace Avalonia.Base.UnitTests Assert.Equal(11, target.GetValue(Class1.FooProperty)); } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_3(BindingPriority priority) + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.FooProperty, source, priority); + source.OnNext(150); + + Assert.Equal(11, target.GetValue(Class1.FooProperty)); + } + [Fact] public void Reverts_To_DefaultValue_Even_In_Presence_Of_Other_Bindings() { From b2c1d80f5cfdf3c0dccbed07cb908b69f7db3e2d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 16:32:02 +0100 Subject: [PATCH 07/76] Fix validation of local value bindings. Local value bindings with a type of `BindingValue` were not being validated. --- .../PropertyStore/LocalValueBindingObserver.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index 0771ade647..5908d9e535 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -77,8 +77,17 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(owner.Owner, property, value); - var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); - owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); + if (value.HasValue) + { + var effectiveValue = value.Value; + if (property.ValidateValue?.Invoke(effectiveValue) == false) + effectiveValue = instance.GetCachedDefaultValue(); + owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); + } + else + { + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); + } } if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError) From 3913fb333eadab53f070e4def78f59e52917a80a Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:50:31 -0500 Subject: [PATCH 08/76] Implement SelectedValue --- .../Primitives/SelectingItemsControl.cs | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 5210362505..2fac60c8d8 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Xml.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Selection; +using Avalonia.Controls.Utils; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; @@ -66,6 +67,19 @@ namespace Avalonia.Controls.Primitives (o, v) => o.SelectedItem = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); + /// + /// Defines the property + /// + public static readonly StyledProperty SelectedValueProperty = + AvaloniaProperty.Register(nameof(SelectedValue), + defaultBindingMode: BindingMode.TwoWay); + + /// + /// Defines the property + /// + public static readonly StyledProperty SelectedValueBindingProperty = + AvaloniaProperty.Register(nameof(SelectedValueBinding)); + /// /// Defines the property. /// @@ -129,6 +143,8 @@ namespace Avalonia.Controls.Primitives private bool _ignoreContainerSelectionChanged; private UpdateState? _updateState; private bool _hasScrolledToSelectedItem; + private BindingHelper? _bindingHelper; + private bool _isSelectionChangeActive; /// /// Initializes static members of the class. @@ -209,6 +225,19 @@ namespace Avalonia.Controls.Primitives } } + [AssignBinding] + public IBinding? SelectedValueBinding + { + get => GetValue(SelectedValueBindingProperty); + set => SetValue(SelectedValueBindingProperty, value); + } + + public object? SelectedValue + { + get => GetValue(SelectedValueProperty); + set => SetValue(SelectedValueProperty, value); + } + /// /// Gets or sets the selected items. /// @@ -609,6 +638,60 @@ namespace Avalonia.Controls.Primitives { WrapFocus = WrapSelection; } + else if (change.Property == SelectedValueProperty) + { + if (_isSelectionChangeActive) + return; + + if (_updateState is not null) + { + _updateState.SelectedValue = change.NewValue; + return; + } + + SelectItemWithValue(change.NewValue); + } + else if (change.Property == SelectedValueBindingProperty) + { + var idx = SelectedIndex; + + // If no selection is active, don't do anything as SelectedValue is already null + if (idx == -1) + { + return; + } + + var value = change.GetNewValue(); + if (value is null) + { + // Clearing SelectedValueBinding makes the SelectedValue the item itself + SelectedValue = SelectedItem; + return; + } + + var selectedItem = SelectedItem; + + try + { + _isSelectionChangeActive = true; + + if (_bindingHelper is null) + { + _bindingHelper = new BindingHelper(value); + } + else + { + _bindingHelper.UpdateBinding(value); + } + + // Re-evaluate SelectedValue with the new binding + SelectedValue = _bindingHelper.Evaluate(selectedItem); + } + finally + { + _isSelectionChangeActive = false; + } + } } /// @@ -815,6 +898,10 @@ namespace Avalonia.Controls.Primitives new BindingValue(SelectedItems)); _oldSelectedItems = SelectedItems; } + else if (e.PropertyName == nameof(ISelectionModel.Source)) + { + ClearValue(SelectedValueProperty); + } } /// @@ -845,6 +932,11 @@ namespace Avalonia.Controls.Primitives Mark(i, false); } + if (!_isSelectionChangeActive) + { + UpdateSelectedValueFromItem(); + } + var route = BuildEventRoute(SelectionChangedEvent); if (route.HasHandlers) @@ -871,6 +963,109 @@ namespace Avalonia.Controls.Primitives } } + private void SelectItemWithValue(object? value) + { + if (ItemCount == 0 || _isSelectionChangeActive) + return; + + try + { + _isSelectionChangeActive = true; + var si = FindItemWithValue(value); + if (si != AvaloniaProperty.UnsetValue) + { + SelectedItem = si; + } + else + { + SelectedItem = null; + } + } + finally + { + _isSelectionChangeActive = false; + } + } + + private object FindItemWithValue(object? value) + { + if (ItemCount == 0 || value is null) + { + return AvaloniaProperty.UnsetValue; + } + + var items = Items; + var binding = SelectedValueBinding; + + if (binding is null) + { + // No SelectedValueBinding set, SelectedValue is the item itself + // Still verify the value passed in is in the Items list + var index = items!.IndexOf(value); + + if (index >= 0) + { + return value; + } + else + { + return AvaloniaProperty.UnsetValue; + } + } + + _bindingHelper ??= new BindingHelper(binding); + + // Matching UWP behavior, if duplicates are present, return the first item matching + // the SelectedValue provided + foreach (var item in items!) + { + var itemValue = _bindingHelper.Evaluate(item); + + if (itemValue.Equals(value)) + { + return item; + } + } + + return AvaloniaProperty.UnsetValue; + } + + private void UpdateSelectedValueFromItem() + { + if (_isSelectionChangeActive) + return; + + var binding = SelectedValueBinding; + var item = SelectedItem; + + if (binding is null || item is null) + { + // No SelectedValueBinding, SelectedValue is Item itself + try + { + _isSelectionChangeActive = true; + SelectedValue = item; + } + finally + { + _isSelectionChangeActive = false; + } + return; + } + + _bindingHelper ??= new BindingHelper(binding); + + try + { + _isSelectionChangeActive = true; + SelectedValue = _bindingHelper.Evaluate(item); + } + finally + { + _isSelectionChangeActive = false; + } + } + private void AutoScrollToSelectedItemIfNecessary() { if (AutoScrollToSelectedItem && @@ -1037,6 +1232,13 @@ namespace Avalonia.Controls.Primitives Selection.Clear(); } + if (state.SelectedValue.HasValue) + { + var item = FindItemWithValue(state.SelectedValue.Value); + if (item != AvaloniaProperty.UnsetValue) + state.SelectedItem = item; + } + if (state.SelectedIndex.HasValue) { SelectedIndex = state.SelectedIndex.Value; @@ -1098,6 +1300,7 @@ namespace Avalonia.Controls.Primitives { private Optional _selectedIndex; private Optional _selectedItem; + private Optional _selectedValue; public int UpdateCount { get; set; } public Optional Selection { get; set; } @@ -1122,6 +1325,54 @@ namespace Avalonia.Controls.Primitives _selectedIndex = default; } } + + public Optional SelectedValue + { + get => _selectedValue; + set + { + _selectedValue = value; + } + } + } + + /// + /// Helper class for evaluating a binding from an Item and IBinding instance + /// + private class BindingHelper : StyledElement + { + public BindingHelper(IBinding binding) + { + UpdateBinding(binding); + } + + public static readonly StyledProperty ValueProperty = + AvaloniaProperty.Register("Value"); + + public object Evaluate(object? dataContext) + { + dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); + + // Only update the DataContext if necessary + if (!dataContext.Equals(DataContext)) + DataContext = dataContext; + + return GetValue(ValueProperty); + } + + public void UpdateBinding(IBinding binding) + { + _lastBinding = binding; + var ib = binding.Initiate(this, ValueProperty); + if (ib is null) + { + throw new InvalidOperationException("Unable to create binding"); + } + + BindingOperations.Apply(this, ValueProperty, ib, null); + } + + private IBinding? _lastBinding; } } } From b9c666e1935182c657436c9e8cd909a262e2cca8 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:50:38 -0500 Subject: [PATCH 09/76] Add Tests --- ...electingItemsControlTests_SelectedValue.cs | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs new file mode 100644 index 0000000000..df81b1faae --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Primitives +{ + public class SelectingItemsControlTests_SelectedValue + { + [Fact] + public void Setting_SelectedItem_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedItem = items[0]; + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedIndex_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedIndex = 0; + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedItems_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new ListBox + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedItems = new List + { + items[1], + items[3], + items[4] + }; + + // When interacting, SelectedItem is the first item in the SelectedItems collection + // But when set here, it's the last + Assert.Equal(items[4].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_Sets_SelectedIndex() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + Prepare(sic); + + sic.SelectedValue = items[1].Name; + + Assert.Equal(1, sic.SelectedIndex); + } + } + + [Fact] + public void Setting_SelectedValue_Sets_SelectedItem() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + Prepare(sic); + + sic.SelectedValue = "Item2"; + + Assert.Equal(items[1], sic.SelectedItem); + } + } + + [Fact] + public void Changing_SelectedValueBinding_Updates_SelectedValue() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedValue = "Item2"; + + sic.SelectedValueBinding = new Binding("AltProperty"); + + // Ensure SelectedItem didn't change + Assert.Equal(items[1], sic.SelectedItem); + + + Assert.Equal("Alt2", sic.SelectedValue); + } + } + + [Fact] + public void SelectedValue_With_Null_SelectedValueBinding_Is_Item() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template() + }; + + sic.SelectedIndex = 0; + + Assert.Equal(items[0], sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_Before_Initialize_Should_Retain_Selection() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + sic.BeginInit(); + sic.EndInit(); + + Assert.Equal(items[1].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_During_Initialize_Should_Take_Priority_Over_Previous_Value() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + sic.BeginInit(); + sic.SelectedValue = "Item1"; + sic.EndInit(); + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Changing_Items_Should_Clear_SelectedValue() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + Prepare(sic); + + sic.Items = new List + { + new TestClass("NewItem", string.Empty) + }; + + Assert.Equal(null, sic.SelectedValue); + } + } + + [Fact] + public void Setting_SelectedValue_Should_Raise_SelectionChanged_Event() + { + // Unlike SelectedIndex/SelectedItem tests, we need the ItemsControl to + // initialize so that SelectedValue can actually be looked up + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + }; + + Prepare(sic); + + var called = false; + sic.SelectionChanged += (s, e) => + { + Assert.Same(items[1], e.AddedItems.Cast().Single()); + Assert.Empty(e.RemovedItems); + called = true; + }; + + sic.SelectedValue = "Item2"; + Assert.True(called); + } + } + + [Fact] + public void Clearing_SelectedValue_Should_Raise_SelectionChanged_Event() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + var called = false; + sic.SelectionChanged += (s, e) => + { + Assert.Same(items[1], e.RemovedItems.Cast().Single()); + Assert.Empty(e.AddedItems); + called = true; + }; + + sic.SelectedValue = null; + Assert.True(called); + } + + private static FuncControlTemplate Template() + { + return new FuncControlTemplate((control, scope) => + new ItemsPresenter + { + Name = "itemsPresenter", + [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], + }.RegisterInNameScope(scope)); + } + + private static void Prepare(SelectingItemsControl target) + { + var root = new TestRoot + { + Child = target, + Width = 100, + Height = 100, + Styles = + { + new Style(x => x.Is()) + { + Setters = + { + new Setter(ListBox.TemplateProperty, Template()), + }, + }, + }, + }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + } + } + + internal class TestClass + { + public TestClass(string name, string alt) + { + Name = name; + AltProperty = alt; + } + + public string Name { get; set; } + + public string AltProperty { get; set; } + + public static List GetItems() + { + return new List + { + new TestClass("Item1", "Alt1"), + new TestClass("Item2", "Alt2"), + new TestClass("Item3", "Alt3"), + new TestClass("Item4", "Alt4"), + new TestClass("Item5", "Alt5"), + }; + } + } +} + + From a8cb64160957920b85c9c865753c42f829a5b294 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 18:02:32 -0500 Subject: [PATCH 10/76] Missing xml docs --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 2fac60c8d8..67ad1963ff 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -225,6 +225,10 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Gets the instance used to obtain the + /// property + /// [AssignBinding] public IBinding? SelectedValueBinding { @@ -232,6 +236,10 @@ namespace Avalonia.Controls.Primitives set => SetValue(SelectedValueBindingProperty, value); } + /// + /// Gets or sets the value of the selected item, obtained using + /// + /// public object? SelectedValue { get => GetValue(SelectedValueProperty); From 467ef300bf559123f3dc355185ad78efdf0675ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Feb 2023 10:43:26 +0100 Subject: [PATCH 11/76] Remove unneeded method. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index f36a96992b..a758360545 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -380,23 +380,6 @@ namespace Avalonia.PropertyStore } } - /// - /// Called by non-LocalValue binding entries to re-evaluate the effective value when the - /// binding produces an unset value. - /// - /// The bound property. - /// The priority of binding which produced a new value. - public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority) - { - Debug.Assert(priority != BindingPriority.LocalValue); - - if (TryGetEffectiveValue(property, out var existing)) - { - if (priority <= existing.Priority) - ReevaluateEffectiveValue(property, existing); - } - } - /// /// Called by a when its /// state changes. From 6210018f3b5e98063252d2bf2933e1739ed17f85 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 3 Feb 2023 14:10:16 +0100 Subject: [PATCH 12/76] Propertly handle an InlineUIContainer's logical children --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 2 +- .../Utils/TreeHelper.cs | 2 +- .../AutoCompleteBox/AutoCompleteBox.cs | 2 +- src/Avalonia.Controls/Control.cs | 4 ---- .../Documents/InlineUIContainer.cs | 18 ++++++++++++++++++ .../Platform/DefaultMenuInteractionHandler.cs | 2 +- .../Primitives/OverlayPopupHost.cs | 2 +- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- src/Avalonia.Controls/Primitives/PopupRoot.cs | 4 ++-- src/Avalonia.Controls/ProgressBar.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 4 ---- 11 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index f35124ee0a..91b65a1f72 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3979,7 +3979,7 @@ namespace Avalonia.Controls { if (focusedObject is Control element) { - parent = element.Parent; + parent = element.VisualParent; if (parent != null) { dataGridWillReceiveRoutedEvent = false; diff --git a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs index f4ba644ae6..6aebf05d6b 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.Utils { if (child is Control childElement) { - parent = childElement.Parent; + parent = childElement.VisualParent; } } child = parent; diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 98885e11ca..55649660f7 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -792,7 +792,7 @@ namespace Avalonia.Controls Control? element = focused as Control; if (element != null) { - parent = element.Parent; + parent = element.VisualParent; } } focused = parent; diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index ed24c3c7c2..ab7c9948c4 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -2,14 +2,12 @@ using System; using System.Collections.Generic; using System.ComponentModel; using Avalonia.Automation.Peers; -using Avalonia.Controls.Documents; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Media; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; @@ -211,8 +209,6 @@ namespace Avalonia.Controls remove => RemoveHandler(SizeChangedEvent, value); } - public new Control? Parent => (Control?)base.Parent; - /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs index 58afb24b5c..f06c8515ee 100644 --- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs +++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs @@ -64,5 +64,23 @@ namespace Avalonia.Controls.Documents internal override void AppendText(StringBuilder stringBuilder) { } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ChildProperty) + { + if(change.OldValue is Control oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if(change.NewValue is Control newChild) + { + LogicalChildren.Add(newChild); + } + } + } } } diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index de3aca76d9..4dd868253e 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -553,7 +553,7 @@ namespace Avalonia.Controls.Platform } } - protected static IMenuItem? GetMenuItem(Control? item) + protected static IMenuItem? GetMenuItem(StyledElement? item) { while (true) { diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index e265f4eb6a..d466edeb33 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls.Primitives set { /* Not currently supported in overlay popups */ } } - protected internal override Interactive? InteractiveParent => Parent; + protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent; public void Dispose() => Hide(); diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index c85199a665..3b68cd2ae8 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -723,7 +723,7 @@ namespace Avalonia.Controls.Primitives while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible)) { - e = e.Parent; + e = e.VisualParent as Control; } if (e is object) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 57ec864cad..b3436d4176 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -72,12 +72,12 @@ namespace Avalonia.Controls.Primitives /// /// Popup events are passed to their parent window. This facilitates this. /// - protected internal override Interactive? InteractiveParent => Parent; + protected internal override Interactive? InteractiveParent => (Interactive?)Parent; /// /// Gets the control that is hosting the popup root. /// - Visual? IHostedVisualTreeRoot.Host => Parent; + Visual? IHostedVisualTreeRoot.Host => VisualParent; /// /// Gets the styling parent of the popup root. diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 7e0d695264..ce3158b282 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -231,7 +231,7 @@ namespace Avalonia.Controls private void UpdateIndicator() { // Gets the size of the parent indicator container - var barSize = _indicator?.Parent?.Bounds.Size ?? Bounds.Size; + var barSize = _indicator?.VisualParent?.Bounds.Size ?? Bounds.Size; if (_indicator != null) { diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index df9a3eb8f3..ec31470126 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -673,8 +673,6 @@ namespace Avalonia.Controls controlRun.Control is Control control) { VisualChildren.Remove(control); - - LogicalChildren.Remove(control); } } } @@ -693,8 +691,6 @@ namespace Avalonia.Controls { VisualChildren.Add(control); - LogicalChildren.Add(control); - control.Measure(Size.Infinity); } } From 757f82c385c7f23b790b3d20e428c55b044c1317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 4 Feb 2023 17:44:14 +0000 Subject: [PATCH 13/76] Updated browser packages to stable. --- .../ControlCatalog.Browser.Blazor.csproj | 4 ++-- .../Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj index d0fb614840..733a4b7194 100644 --- a/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj +++ b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj index a9cad0538f..9017ce1546 100644 --- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj +++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj @@ -15,7 +15,7 @@ - + From d0fe9e0e14f3dbe72ece505ddb2d2ea88606d371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 4 Feb 2023 18:06:42 +0000 Subject: [PATCH 14/76] Updated Skia and HarfBuzz to latest stable. --- build/HarfBuzzSharp.props | 6 +++--- build/SkiaSharp.props | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props index 620ec58ff3..75d317be1a 100644 --- a/build/HarfBuzzSharp.props +++ b/build/HarfBuzzSharp.props @@ -1,7 +1,7 @@  - - - + + + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 31619399f9..f45addaa2a 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,7 +1,7 @@  - - - + + + From 9fdf98dde6e4d466e9ff72bf9635f8fdad1455f6 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:18:35 -0500 Subject: [PATCH 15/76] Only use font fallback if match found --- .../Media/TextFormatting/TextCharacters.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index c9dafaced7..8480be3882 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -118,14 +118,18 @@ namespace Avalonia.Media.TextFormatting fontManager.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo, out var fallbackTypeface); - - var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); - - if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) + + if (matchFound) { + var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); + //Fallback found - return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), - biDiLevel); + if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) + { + + return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), + biDiLevel); + } } // no fallback found From 1cfdae9448d9eac12bba5ac02ac859ab7e459134 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:23:03 -0500 Subject: [PATCH 16/76] Cleanup --- src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 8480be3882..b4734d702b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -121,12 +121,11 @@ namespace Avalonia.Media.TextFormatting if (matchFound) { + // Fallback found var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); - - //Fallback found + if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) - { - + { return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), biDiLevel); } From fe2233d25a53e654ff82705cba3069319538743c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sat, 4 Feb 2023 22:03:50 +0100 Subject: [PATCH 17/76] Update ItemsPresenter.cs --- src/Avalonia.Controls/Presenters/ItemsPresenter.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 8594b584fa..e8eaac7d17 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -28,19 +28,19 @@ namespace Avalonia.Controls.Presenters /// Defines the property. /// public static readonly StyledProperty AreHorizontalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular)); + AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular)); /// /// Defines the property. /// public static readonly StyledProperty AreVerticalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular)); + AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular)); /// /// Defines the event. /// public static readonly RoutedEvent HorizontalSnapPointsChangedEvent = - RoutedEvent.Register( + RoutedEvent.Register( nameof(HorizontalSnapPointsChanged), RoutingStrategies.Bubble); @@ -48,7 +48,7 @@ namespace Avalonia.Controls.Presenters /// Defines the event. /// public static readonly RoutedEvent VerticalSnapPointsChangedEvent = - RoutedEvent.Register( + RoutedEvent.Register( nameof(VerticalSnapPointsChanged), RoutingStrategies.Bubble); @@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default; /// - /// Gets or sets whether the horizontal snap points for the are equidistant from each other. + /// Gets or sets whether the horizontal snap points for the are equidistant from each other. /// public bool AreHorizontalSnapPointsRegular { @@ -148,7 +148,7 @@ namespace Avalonia.Controls.Presenters } /// - /// Gets or sets whether the vertical snap points for the are equidistant from each other. + /// Gets or sets whether the vertical snap points for the are equidistant from each other. /// public bool AreVerticalSnapPointsRegular { From 465d72f2903572c66a444f9f2b31392b7ad6cd69 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sat, 4 Feb 2023 16:46:13 -0700 Subject: [PATCH 18/76] Add nullable ref annotation to Windows.Close --- src/Avalonia.Controls/Window.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index a20b4eee58..b1110ece55 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -450,7 +450,7 @@ namespace Avalonia.Controls /// resulting task will produce the value when the window /// is closed. /// - public void Close(object dialogResult) + public void Close(object? dialogResult) { _dialogResult = dialogResult; CloseCore(WindowCloseReason.WindowClosing, true); From f8af158a38bd1718126d0b2e3fc35d8b89a18ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 4 Feb 2023 17:35:08 +0000 Subject: [PATCH 19/76] Updated packages used by tests. --- build/ImageSharp.props | 2 +- build/Moq.props | 2 +- build/XUnit.props | 15 +++++++-------- .../Rendering/SceneGraph/DrawOperationTests.cs | 4 ++-- .../Avalonia.Benchmarks.csproj | 2 +- .../Avalonia.IntegrationTests.Appium.csproj | 2 +- .../Avalonia.LeakTests/Avalonia.LeakTests.csproj | 2 +- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/build/ImageSharp.props b/build/ImageSharp.props index 178c274ac9..66e6580070 100644 --- a/build/ImageSharp.props +++ b/build/ImageSharp.props @@ -1,5 +1,5 @@ - + diff --git a/build/Moq.props b/build/Moq.props index 9e2fd1db5d..357f0c9a5f 100644 --- a/build/Moq.props +++ b/build/Moq.props @@ -1,5 +1,5 @@  - + diff --git a/build/XUnit.props b/build/XUnit.props index 17ead91aa3..3c89c8b52b 100644 --- a/build/XUnit.props +++ b/build/XUnit.props @@ -1,13 +1,12 @@  - - - - - - - - + + + + + + + diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 07d2d672ae..c1468a28e4 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph double height, double scaleX, double scaleY, - double? penThickness, + double penThickness, double expectedX, double expectedY, double expectedWidth, @@ -38,7 +38,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph var target = new TestRectangleDrawOperation( new Rect(x, y, width, height), Matrix.CreateScale(scaleX, scaleY), - penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null); + new Pen(Brushes.Black, penThickness)); Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds); } diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 0ddee2ad7a..9ea0482abc 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj index 3ff91139f1..5de2b85569 100644 --- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj +++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index 15815c81b6..c3d9aa0622 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -1,6 +1,6 @@  - net461 + net462 From 8c07476a1a61ff0d70af12672697b301958394b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sun, 5 Feb 2023 09:01:44 +0000 Subject: [PATCH 20/76] Fixed NuGet package authors. --- build/SharedVersion.props | 1 + 1 file changed, 1 insertion(+) diff --git a/build/SharedVersion.props b/build/SharedVersion.props index eca3ba37b0..2849262591 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -3,6 +3,7 @@ Avalonia 11.0.999 + Avalonia Team Copyright 2022 © The AvaloniaUI Project https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ From cc850694cdf17aaca4fc48571090620371fe4e1f Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 5 Feb 2023 12:00:38 +0100 Subject: [PATCH 21/76] editorconfig: don't apply s_ prefix to public fields --- .editorconfig | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.editorconfig b/.editorconfig index e6ae266849..a144ec8843 100644 --- a/.editorconfig +++ b/.editorconfig @@ -55,16 +55,17 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# static fields should have s_ prefix -dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +# private static fields should have s_ prefix +dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.required_modifiers = static +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private -dotnet_naming_style.static_prefix_style.required_prefix = s_ -dotnet_naming_style.static_prefix_style.capitalization = camel_case +dotnet_naming_style.private_static_prefix_style.required_prefix = s_ +dotnet_naming_style.private_static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion @@ -117,7 +118,7 @@ csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = do_not_ignore +csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false @@ -211,5 +212,5 @@ indent_size = 2 # Shell scripts [*.sh] end_of_line = lf -[*.{cmd, bat}] +[*.{cmd,bat}] end_of_line = crlf From 7eda49e0616dfed92badbfb1dfddec952e377612 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 5 Feb 2023 22:33:45 +0100 Subject: [PATCH 22/76] Ensure IRenderRoot.Renderer is never null --- .../Composition/CompositingRenderer.cs | 82 +++++++++++++------ src/Avalonia.Base/Rendering/IRenderRoot.cs | 2 - src/Avalonia.Base/Rendering/IRenderer.cs | 3 + src/Avalonia.Base/Visual.cs | 10 +-- src/Avalonia.Controls/TopLevel.cs | 40 +++------ src/Avalonia.Controls/Window.cs | 6 +- src/Avalonia.Controls/WindowBase.cs | 6 +- .../Diagnostics/Controls/Application.cs | 2 +- src/Avalonia.Native/WindowImpl.cs | 3 +- .../ButtonTests.cs | 20 ++--- .../TopLevelTests.cs | 39 +++++---- .../Utils/HotKeyManagerTests.cs | 12 ++- .../WindowBaseTests.cs | 14 +++- .../WindowTests.cs | 2 + .../WindowingPlatformMock.cs | 35 -------- tests/Avalonia.UnitTests/TestTemplatedRoot.cs | 54 ------------ 16 files changed, 138 insertions(+), 192 deletions(-) delete mode 100644 tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs delete mode 100644 tests/Avalonia.UnitTests/TestTemplatedRoot.cs diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 668d650ffd..7fa2d4955f 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -20,15 +20,17 @@ public class CompositingRenderer : IRendererWithCompositor { private readonly IRenderRoot _root; private readonly Compositor _compositor; - CompositionDrawingContext _recorder = new(); - DrawingContext _recordingContext; - private HashSet _dirty = new(); - private HashSet _recalculateChildren = new(); + private readonly CompositionDrawingContext _recorder = new(); + private readonly DrawingContext _recordingContext; + private readonly HashSet _dirty = new(); + private readonly HashSet _recalculateChildren = new(); + private readonly Action _update; + private bool _queuedUpdate; - private Action _update; private bool _updating; + private bool _isDisposed; - internal CompositionTarget CompositionTarget; + internal CompositionTarget CompositionTarget { get; } /// /// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered. @@ -38,6 +40,17 @@ public class CompositingRenderer : IRendererWithCompositor /// public RendererDiagnostics Diagnostics { get; } + /// + public Compositor Compositor => _compositor; + + /// + /// Initializes a new instance of + /// + /// The render root using this renderer. + /// The associated compositors. + /// + /// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems. + /// public CompositingRenderer(IRenderRoot root, Compositor compositor, Func> surfaces) { _root = root; @@ -66,7 +79,7 @@ public class CompositingRenderer : IRendererWithCompositor /// public event EventHandler? SceneInvalidated; - void QueueUpdate() + private void QueueUpdate() { if(_queuedUpdate) return; @@ -77,9 +90,11 @@ public class CompositingRenderer : IRendererWithCompositor /// public void AddDirty(Visual visual) { + if (_isDisposed) + return; if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); - _dirty.Add((Visual)visual); + _dirty.Add(visual); QueueUpdate(); } @@ -126,9 +141,11 @@ public class CompositingRenderer : IRendererWithCompositor /// public void RecalculateChildren(Visual visual) { + if (_isDisposed) + return; if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); - _recalculateChildren.Add((Visual)visual); + _recalculateChildren.Add(visual); QueueUpdate(); } @@ -171,7 +188,7 @@ public class CompositingRenderer : IRendererWithCompositor if (sortedChildren != null) for (var c = 0; c < visualChildren.Count; c++) { - if (!ReferenceEquals(compositionChildren[c], ((Visual)sortedChildren[c].visual).CompositionVisual)) + if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual)) { mismatch = true; break; @@ -179,7 +196,7 @@ public class CompositingRenderer : IRendererWithCompositor } else for (var c = 0; c < visualChildren.Count; c++) - if (!ReferenceEquals(compositionChildren[c], ((Visual)visualChildren[c]).CompositionVisual)) + if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual)) { mismatch = true; break; @@ -201,7 +218,7 @@ public class CompositingRenderer : IRendererWithCompositor { foreach (var ch in sortedChildren) { - var compositionChild = ((Visual)ch.visual).CompositionVisual; + var compositionChild = ch.visual.CompositionVisual; if (compositionChild != null) compositionChildren.Add(compositionChild); } @@ -210,7 +227,7 @@ public class CompositingRenderer : IRendererWithCompositor else foreach (var ch in v.GetVisualChildren()) { - var compositionChild = ((Visual)ch).CompositionVisual; + var compositionChild = ch.CompositionVisual; if (compositionChild != null) compositionChildren.Add(compositionChild); } @@ -289,13 +306,18 @@ public class CompositingRenderer : IRendererWithCompositor _updating = false; } } - + + /// public void Resized(Size size) { } + /// public void Paint(Rect rect) { + if (_isDisposed) + return; + QueueUpdate(); CompositionTarget.RequestRedraw(); if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground) @@ -304,17 +326,34 @@ public class CompositingRenderer : IRendererWithCompositor CompositionTarget.ImmediateUIThreadRender(); } - public void Start() => CompositionTarget.IsEnabled = true; - - public void Stop() + /// + public void Start() { - CompositionTarget.IsEnabled = false; + if (_isDisposed) + return; + + CompositionTarget.IsEnabled = true; } - public ValueTask TryGetRenderInterfaceFeature(Type featureType) => Compositor.TryGetRenderInterfaceFeature(featureType); + /// + public void Stop() + => CompositionTarget.IsEnabled = false; + + /// + public ValueTask TryGetRenderInterfaceFeature(Type featureType) + => Compositor.TryGetRenderInterfaceFeature(featureType); + /// public void Dispose() { + if (_isDisposed) + return; + + _isDisposed = true; + _dirty.Clear(); + _recalculateChildren.Clear(); + SceneInvalidated = null; + Stop(); CompositionTarget.Dispose(); @@ -323,9 +362,4 @@ public class CompositingRenderer : IRendererWithCompositor if (Compositor.Loop.RunsInBackground) _compositor.Commit().Wait(); } - - /// - /// The associated object - /// - public Compositor Compositor => _compositor; } diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index fa3260ffb4..09df7b7830 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -1,6 +1,4 @@ using Avalonia.Metadata; -using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering { diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs index ba960ff5f3..7e32504e17 100644 --- a/src/Avalonia.Base/Rendering/IRenderer.cs +++ b/src/Avalonia.Base/Rendering/IRenderer.cs @@ -90,6 +90,9 @@ namespace Avalonia.Rendering public interface IRendererWithCompositor : IRenderer { + /// + /// The associated object + /// Compositor Compositor { get; } } } diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index e6d7492c51..fc215fc890 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -348,7 +348,7 @@ namespace Avalonia /// public void InvalidateVisual() { - VisualRoot?.Renderer?.AddDirty(this); + VisualRoot?.Renderer.AddDirty(this); } /// @@ -449,7 +449,7 @@ namespace Avalonia protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { base.LogicalChildrenCollectionChanged(sender, e); - VisualRoot?.Renderer?.RecalculateChildren(this); + VisualRoot?.Renderer.RecalculateChildren(this); } /// @@ -477,7 +477,7 @@ namespace Avalonia OnAttachedToVisualTree(e); AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); - _visualRoot.Renderer?.RecalculateChildren(_visualParent!); + _visualRoot.Renderer.RecalculateChildren(_visualParent!); if (ZIndex != 0 && VisualParent is Visual parent) parent.HasNonUniformZIndexChildren = true; @@ -540,7 +540,7 @@ namespace Avalonia } DetachedFromVisualTree?.Invoke(this, e); - e.Root?.Renderer?.AddDirty(this); + e.Root.Renderer.AddDirty(this); var visualChildren = VisualChildren; @@ -659,7 +659,7 @@ namespace Avalonia parentVisual.HasNonUniformZIndexChildren = true; sender?.InvalidateVisual(); - parent?.VisualRoot?.Renderer?.RecalculateChildren(parent); + parent?.VisualRoot?.Renderer.RecalculateChildren(parent); } /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 676fa1519a..4db71abfa8 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -94,7 +94,6 @@ namespace Avalonia.Controls private readonly IInputManager? _inputManager; private readonly IAccessKeyHandler? _accessKeyHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; - private readonly IPlatformRenderInterface? _renderInterface; private readonly IGlobalStyles? _globalStyles; private readonly IGlobalThemeVariantProvider? _applicationThemeHost; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; @@ -136,36 +135,21 @@ namespace Avalonia.Controls /// public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver) { - if (impl == null) - { - throw new InvalidOperationException( - "Could not create window implementation: maybe no windowing subsystem was initialized?"); - } - - PlatformImpl = impl; + PlatformImpl = impl ?? throw new InvalidOperationException( + "Could not create window implementation: maybe no windowing subsystem was initialized?"); _actualTransparencyLevel = PlatformImpl.TransparencyLevel; - dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current; + dependencyResolver ??= AvaloniaLocator.Current; _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); - _renderInterface = TryGetService(dependencyResolver); _globalStyles = TryGetService(dependencyResolver); _applicationThemeHost = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); - - if (Renderer != null) - { - Renderer.SceneInvalidated += SceneInvalidated; - } - else - { - // Prevent nullable error. - Renderer = null!; - } + Renderer.SceneInvalidated += SceneInvalidated; impl.SetInputRoot(this); @@ -216,7 +200,7 @@ namespace Avalonia.Controls if(impl.TryGetFeature() is {} systemNavigationManager) { - systemNavigationManager.BackRequested += (s, e) => + systemNavigationManager.BackRequested += (_, e) => { e.RoutedEvent = BackRequestedEvent; RaiseEvent(e); @@ -337,7 +321,7 @@ namespace Avalonia.Controls { _layoutManager = CreateLayoutManager(); - if (_layoutManager is LayoutManager typedLayoutManager && Renderer is not null) + if (_layoutManager is LayoutManager typedLayoutManager) { _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager); _layoutDiagnosticBridge.SetupBridge(); @@ -356,7 +340,7 @@ namespace Avalonia.Controls /// /// Gets the renderer for the window. /// - public IRenderer Renderer { get; private set; } + public IRenderer Renderer { get; } internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition; @@ -450,7 +434,7 @@ namespace Avalonia.Controls /// The dirty area. protected virtual void HandlePaint(Rect rect) { - Renderer?.Paint(rect); + Renderer.Paint(rect); } /// @@ -468,8 +452,8 @@ namespace Avalonia.Controls _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; } - Renderer?.Dispose(); - Renderer = null!; + Renderer.SceneInvalidated -= SceneInvalidated; + Renderer.Dispose(); _layoutDiagnosticBridge?.Dispose(); _layoutDiagnosticBridge = null; @@ -488,7 +472,7 @@ namespace Avalonia.Controls OnClosed(EventArgs.Empty); - LayoutManager?.Dispose(); + LayoutManager.Dispose(); } /// @@ -503,7 +487,7 @@ namespace Avalonia.Controls Width = clientSize.Width; Height = clientSize.Height; LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); + Renderer.Resized(clientSize); } /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index b1110ece55..ba1b599421 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -573,7 +573,7 @@ namespace Avalonia.Controls return; } - Renderer?.Stop(); + Renderer.Stop(); if (Owner is Window owner) { @@ -721,7 +721,7 @@ namespace Avalonia.Controls SetWindowStartupLocation(owner?.PlatformImpl); PlatformImpl?.Show(ShowActivated, false); - Renderer?.Start(); + Renderer.Start(); OnOpened(EventArgs.Empty); } } @@ -798,7 +798,7 @@ namespace Avalonia.Controls PlatformImpl?.Show(ShowActivated, true); - Renderer?.Start(); + Renderer.Start(); Observable.FromEventPattern( x => Closed += x, diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index aad0482b50..0c9a91148b 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -129,7 +129,7 @@ namespace Avalonia.Controls { using (FreezeVisibilityChangeHandling()) { - Renderer?.Stop(); + Renderer.Stop(); PlatformImpl?.Hide(); IsVisible = false; } @@ -153,7 +153,7 @@ namespace Avalonia.Controls } PlatformImpl?.Show(true, false); - Renderer?.Start(); + Renderer.Start(); OnOpened(EventArgs.Empty); } } @@ -219,7 +219,7 @@ namespace Avalonia.Controls { ClientSize = clientSize; LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); + Renderer.Resized(clientSize); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 7426c4e2ed..a0ff3a714f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.Controls RendererRoot = application.ApplicationLifetime switch { Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow?.Renderer, - Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as Visual)?.VisualRoot?.Renderer, + Lifetimes.ISingleViewApplicationLifetime single => single.MainView?.VisualRoot?.Renderer, _ => null }; diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index f27d94b61a..64c1d0da10 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -119,7 +119,8 @@ namespace Avalonia.Native { if(e.Type == RawPointerEventType.LeftButtonDown) { - var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x => + var window = _inputRoot as Window; + var visual = window?.Renderer.HitTestFirst(e.Position, window, x => { if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) { diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 4ff98bdedd..2679d4ce06 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -140,10 +140,9 @@ namespace Avalonia.Controls.UnitTests .Returns>((p, r, f) => r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); - var target = new TestButton() + var target = new TestButton(renderer.Object) { - Bounds = new Rect(0, 0, 100, 100), - Renderer = renderer.Object + Bounds = new Rect(0, 0, 100, 100) }; bool clicked = false; @@ -172,10 +171,9 @@ namespace Avalonia.Controls.UnitTests .Returns>((p, r, f) => r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); - var target = new TestButton() + var target = new TestButton(renderer.Object) { - Bounds = new Rect(0, 0, 100, 100), - Renderer = renderer.Object + Bounds = new Rect(0, 0, 100, 100) }; bool clicked = false; @@ -206,11 +204,10 @@ namespace Avalonia.Controls.UnitTests r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ? new Visual[] { r } : new Visual[0]); - var target = new TestButton() + var target = new TestButton(renderer.Object) { Bounds = new Rect(0, 0, 100, 100), - RenderTransform = new TranslateTransform { X = 100, Y = 0 }, - Renderer = renderer.Object + RenderTransform = new TranslateTransform { X = 100, Y = 0 } }; //actual bounds of button should be 100,0,100,100 x -> translated 100 pixels @@ -386,9 +383,10 @@ namespace Avalonia.Controls.UnitTests private class TestButton : Button, IRenderRoot { - public TestButton() + public TestButton(IRenderer renderer) { IsVisible = true; + Renderer = renderer; } public new Rect Bounds @@ -399,7 +397,7 @@ namespace Avalonia.Controls.UnitTests public Size ClientSize => throw new NotImplementedException(); - public IRenderer Renderer { get; set; } + public IRenderer Renderer { get; } public double RenderScaling => throw new NotImplementedException(); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index d63251c1f5..2644e7184a 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -6,6 +6,7 @@ using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -20,7 +21,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); var target = new TestTopLevel(impl.Object); Assert.True(((ILogical)target).IsAttachedToLogicalTree); @@ -32,7 +33,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -46,7 +47,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -60,7 +61,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -76,7 +77,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(services)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); var target = new TestTopLevel(impl.Object, Mock.Of()); @@ -91,7 +92,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupProperty(x => x.Resized); impl.SetupGet(x => x.RenderScaling).Returns(1); @@ -117,7 +118,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); @@ -151,7 +152,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); bool raised = false; @@ -169,7 +170,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); @@ -200,7 +201,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(services)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); @@ -222,7 +223,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); var child = new TestTopLevel(impl.Object); @@ -240,7 +241,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); var raised = false; @@ -257,7 +258,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var layoutManager = new Mock(); @@ -274,7 +275,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupGet(x => x.RenderScaling).Returns(1); var child = new Border { Classes = { "foo" } }; @@ -317,6 +318,14 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope)); } + private static Mock CreateMockTopLevelImpl() + { + var renderer = new Mock(); + renderer.Setup(r => r.CreateRenderer(It.IsAny())) + .Returns(RendererMocks.CreateRenderer().Object); + return renderer; + } + private class TestTopLevel : TopLevel { private readonly ILayoutManager _layoutManager; diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index f367112cc0..336aad79da 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -4,9 +4,7 @@ using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Styling; using Avalonia.UnitTests; -using Moq; using Xunit; using Factory = System.Func, Avalonia.Controls.Window, Avalonia.AvaloniaObject>; @@ -20,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Utils using (AvaloniaLocator.EnterScope()) { AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control); @@ -64,7 +62,7 @@ namespace Avalonia.Controls.UnitTests.Utils var commandResult = 0; var expectedParameter = 1; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -106,7 +104,7 @@ namespace Avalonia.Controls.UnitTests.Utils var target = new KeyboardDevice(); var isExecuted = false; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -146,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Utils var target = new KeyboardDevice(); var clickExecutedCount = 0; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -199,7 +197,7 @@ namespace Avalonia.Controls.UnitTests.Utils var clickExecutedCount = 0; var commandExecutedCount = 0; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index a10b1324d6..d65fa06183 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockWindowBaseImpl(); var target = new TestWindowBase(impl.Object); target.Activate(); @@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockWindowBaseImpl(); impl.SetupAllProperties(); bool raised = false; @@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockWindowBaseImpl(); impl.SetupAllProperties(); bool raised = false; @@ -241,6 +241,14 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope)); } + private static Mock CreateMockWindowBaseImpl() + { + var renderer = new Mock(); + renderer.Setup(r => r.CreateRenderer(It.IsAny())) + .Returns(RendererMocks.CreateRenderer().Object); + return renderer; + } + private class TestWindowBase : WindowBase { public bool IsClosed { get; private set; } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 014174990e..cada2bfa6f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests public void Setting_Title_Should_Set_Impl_Title() { var windowImpl = new Mock(); + windowImpl.Setup(r => r.CreateRenderer(It.IsAny())) + .Returns(RendererMocks.CreateRenderer().Object); var windowingPlatform = new MockWindowingPlatform(() => windowImpl.Object); using (UnitTestApplication.Start(new TestServices(windowingPlatform: windowingPlatform))) diff --git a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs deleted file mode 100644 index e8471d41fb..0000000000 --- a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Moq; -using Avalonia.Platform; - -namespace Avalonia.Controls.UnitTests -{ - public class WindowingPlatformMock : IWindowingPlatform - { - private readonly Func _windowImpl; - private readonly Func _popupImpl; - - public WindowingPlatformMock(Func windowImpl = null, Func popupImpl = null ) - { - _windowImpl = windowImpl; - _popupImpl = popupImpl; - } - - public IWindowImpl CreateWindow() - { - return _windowImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); - } - - public IWindowImpl CreateEmbeddableWindow() - { - throw new NotImplementedException(); - } - - public ITrayIconImpl CreateTrayIcon() - { - return null; - } - - public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); - } -} diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs deleted file mode 100644 index 38ab3c3c5d..0000000000 --- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Layout; -using Avalonia.LogicalTree; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Styling; - -namespace Avalonia.UnitTests -{ - public class TestTemplatedRoot : ContentControl, ILayoutRoot, IRenderRoot, ILogicalRoot - { - private readonly NameScope _nameScope = new NameScope(); - - public TestTemplatedRoot() - { - LayoutManager = new LayoutManager(this); - Template = new FuncControlTemplate((x, scope) => new ContentPresenter - { - Name = "PART_ContentPresenter", - }.RegisterInNameScope(scope)); - } - - public Size ClientSize => new Size(100, 100); - - public Size MaxClientSize => Size.Infinity; - - public double LayoutScaling => 1; - - public ILayoutManager LayoutManager { get; set; } - - public double RenderScaling => 1; - - public IRenderTarget RenderTarget => null; - - public IRenderer Renderer => null; - - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } - - public void Invalidate(Rect rect) - { - throw new NotImplementedException(); - } - - public Point PointToClient(PixelPoint p) => p.ToPoint(1); - - public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); - } -} From 1642129d0f7477ce0b02c38d8482f253525f2d41 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 6 Feb 2023 04:07:02 -0500 Subject: [PATCH 23/76] Implement simple PreloadDepsAssemblies --- .../DesignXamlLoader.cs | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs index 181883656c..690926a193 100644 --- a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs +++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs @@ -1,16 +1,79 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.XamlIl; -namespace Avalonia.Designer.HostApp +namespace Avalonia.Designer.HostApp; + +class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader { - class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader + public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) + { + PreloadDepsAssemblies(configuration.LocalAssembly ?? Assembly.GetEntryAssembly()); + + return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration); + } + + private void PreloadDepsAssemblies(Assembly targetAssembly) { - public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) + // Assemblies loaded in memory (e.g. single file) return empty string from Location. + // In these cases, don't try probing next to the assembly. + var assemblyLocation = targetAssembly.Location; + if (string.IsNullOrEmpty(assemblyLocation)) + { + return; + } + + var depsJsonFile = Path.ChangeExtension(assemblyLocation, ".deps.json"); + if (!File.Exists(depsJsonFile)) + { + return; + } + + using var stream = File.OpenRead(depsJsonFile); + + /* + We can't use any references in the Avalonia.Designer.HostApp. Including even json. + Ideally we would prefer Microsoft.Extensions.DependencyModel package, but can't use it here. + So, instead we need to fallback to some JSON parsing using pretty easy regex. + + Json part example: +"Avalonia.Xaml.Interactions/11.0.0-preview5": { + "dependencies": { + "Avalonia": "11.0.999", + "Avalonia.Xaml.Interactivity": "11.0.0-preview5" + }, + "runtime": { + "lib/net6.0/Avalonia.Xaml.Interactions.dll": { + "assemblyVersion": "11.0.0.0", + "fileVersion": "11.0.0.0" + } + } +}, + We want to extract "lib/net6.0/Avalonia.Xaml.Interactions.dll" from here. + No need to resolve real path of ref assemblies. + No need to handle special cases with .NET Framework and GAC. + */ + var text = new StreamReader(stream).ReadToEnd(); + var matches = Regex.Matches( text, """runtime"\s*:\s*{\s*"([^"]+)"""); + + foreach (Match match in matches) { - return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration); + if (match.Groups[1] is { Success: true } g) + { + var assemblyName = Path.GetFileNameWithoutExtension(g.Value); + try + { + _ = Assembly.Load(new AssemblyName(assemblyName)); + } + catch + { + } + } } } } From 39fe9b17fb346ee5e63d6bfa508ed8d775eda355 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 6 Feb 2023 19:26:11 +0100 Subject: [PATCH 24/76] Nullability fixes for Avalonia.Base --- src/Avalonia.Base/Animation/KeySpline.cs | 11 ++- src/Avalonia.Base/AvaloniaObject.cs | 38 ++++------- .../Diagnostics/AvaloniaObjectExtensions.cs | 3 - src/Avalonia.Base/Input/DragEventArgs.cs | 18 ++--- src/Avalonia.Base/Input/KeyGesture.cs | 2 +- .../Input/KeyboardNavigationHandler.cs | 44 ++++++------ .../Input/Navigation/TabNavigation.cs | 48 ++++--------- .../LogicalTree/LogicalExtensions.cs | 6 +- src/Avalonia.Base/Media/Color.cs | 9 +-- src/Avalonia.Base/Media/DrawingContext.cs | 2 +- src/Avalonia.Base/Media/DrawingGroup.cs | 26 +++---- src/Avalonia.Base/Media/DrawingImage.cs | 6 +- src/Avalonia.Base/Media/FontFamily.cs | 4 +- .../Media/Fonts/FontFamilyKey.cs | 5 +- src/Avalonia.Base/Media/FormattedText.cs | 6 +- src/Avalonia.Base/Media/GeometryDrawing.cs | 6 +- src/Avalonia.Base/Media/GlyphRunDrawing.cs | 12 ++-- src/Avalonia.Base/Media/HslColor.cs | 2 +- src/Avalonia.Base/Media/HsvColor.cs | 2 +- src/Avalonia.Base/Media/IVisualBrush.cs | 3 +- .../Media/Immutable/ImmutableDashStyle.cs | 24 ++----- .../Media/Immutable/ImmutableVisualBrush.cs | 7 +- src/Avalonia.Base/Media/TextDecoration.cs | 14 ++-- src/Avalonia.Base/Media/VisualBrush.cs | 7 +- .../Platform/IDrawingContextImpl.cs | 4 +- .../Platform/Internal/AssemblyDescriptor.cs | 28 ++++---- .../Animations/CompositionAnimation.cs | 2 +- .../Animations/CompositionAnimationGroup.cs | 2 +- .../Animations/ImplicitAnimationCollection.cs | 2 +- .../Composition/CompositionDrawingSurface.cs | 3 +- .../Composition/CompositionObject.cs | 4 +- .../Composition/CompositionPropertySet.cs | 2 +- .../Drawing/CompositionDrawingContext.cs | 14 +++- .../Composition/Expressions/Expression.cs | 2 - .../ExpressionEvaluationContext.cs | 1 - .../Composition/Server/DrawingContextProxy.cs | 4 +- .../Rendering/ImmediateRenderer.cs | 6 +- .../SceneGraph/ExperimentalAcrylicNode.cs | 7 +- src/Avalonia.Base/Utilities/TypeUtilities.cs | 11 +-- src/Avalonia.Base/Utilities/WeakEvent.cs | 68 ++----------------- .../Utilities/WeakEventHandlerManager.cs | 45 ++++++------ src/Avalonia.Base/Visual.cs | 24 +++---- .../VisualTree/VisualExtensions.cs | 19 +++--- .../HeadlessPlatformRenderInterface.cs | 4 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 12 ++++ .../Avalonia.Direct2D1/Media/GlyphRunImpl.cs | 7 +- tests/Avalonia.UnitTests/MockGlyphRun.cs | 8 +-- 47 files changed, 226 insertions(+), 358 deletions(-) diff --git a/src/Avalonia.Base/Animation/KeySpline.cs b/src/Avalonia.Base/Animation/KeySpline.cs index 6ca5b2e759..ed6adb79b8 100644 --- a/src/Avalonia.Base/Animation/KeySpline.cs +++ b/src/Avalonia.Base/Animation/KeySpline.cs @@ -79,15 +79,12 @@ namespace Avalonia.Animation /// culture of the string /// Thrown if the string does not have 4 values /// A with the appropriate values set - public static KeySpline Parse(string value, CultureInfo culture) + public static KeySpline Parse(string value, CultureInfo? culture) { - if (culture is null) - culture = CultureInfo.InvariantCulture; + culture ??= CultureInfo.InvariantCulture; - using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".")) - { - return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); - } + using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."); + return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); } /// diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1946d4ba5c..50a7a5c831 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -152,7 +152,7 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); - _values?.ClearLocalValue(property); + _values.ClearLocalValue(property); } /// @@ -242,7 +242,14 @@ namespace Avalonia return registered.InvokeGetter(this); } - /// + /// + /// Gets an base value. + /// + /// The property. + /// + /// Gets the value of the property excluding animated values, otherwise . + /// Note that this method does not return property values that come from inherited or default values. + /// public Optional GetBaseValue(StyledProperty property) { _ = property ?? throw new ArgumentNullException(nameof(property)); @@ -261,7 +268,7 @@ namespace Avalonia VerifyAccess(); - return _values?.IsAnimating(property) ?? false; + return _values.IsAnimating(property); } /// @@ -279,7 +286,7 @@ namespace Avalonia VerifyAccess(); - return _values?.IsSet(property) ?? false; + return _values.IsSet(property); } /// @@ -515,14 +522,12 @@ namespace Avalonia /// The property. public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property); - /// internal void AddInheritanceChild(AvaloniaObject child) { _inheritanceChildren ??= new List(); _inheritanceChildren.Add(child); } - - /// + internal void RemoveInheritanceChild(AvaloniaObject child) { _inheritanceChildren?.Remove(child); @@ -541,24 +546,11 @@ namespace Avalonia return new AvaloniaPropertyValue( property, GetValue(property), - BindingPriority.Unset, - "Local Value"); - } - else if (_values != null) - { - var result = _values.GetDiagnostic(property); - - if (result != null) - { - return result; - } + BindingPriority.LocalValue, + null); } - return new AvaloniaPropertyValue( - property, - GetValue(property), - BindingPriority.Unset, - "Unset"); + return _values.GetDiagnostic(property); } internal ValueStore GetValueStore() => _values; diff --git a/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs index d7b1f2e053..270cac95f2 100644 --- a/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs @@ -1,6 +1,3 @@ -using System; -using Avalonia.Data; - namespace Avalonia.Diagnostics { /// diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 403dd6f23e..8d7cc2b9a1 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -1,36 +1,28 @@ using System; using Avalonia.Interactivity; using Avalonia.Metadata; -using Avalonia.VisualTree; namespace Avalonia.Input { public class DragEventArgs : RoutedEventArgs { - private Interactive _target; - private Point _targetLocation; + private readonly Interactive _target; + private readonly Point _targetLocation; public DragDropEffects DragEffects { get; set; } - public IDataObject Data { get; private set; } + public IDataObject Data { get; } - public KeyModifiers KeyModifiers { get; private set; } + public KeyModifiers KeyModifiers { get; } public Point GetPosition(Visual relativeTo) { - var point = new Point(0, 0); - if (relativeTo == null) { throw new ArgumentNullException(nameof(relativeTo)); } - if (_target != null) - { - point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point; - } - - return point; + return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0); } [Unstable] diff --git a/src/Avalonia.Base/Input/KeyGesture.cs b/src/Avalonia.Base/Input/KeyGesture.cs index c6618fd550..9ee8ae9711 100644 --- a/src/Avalonia.Base/Input/KeyGesture.cs +++ b/src/Avalonia.Base/Input/KeyGesture.cs @@ -136,7 +136,7 @@ namespace Avalonia.Input return StringBuilderCache.GetStringAndRelease(s); } - public bool Matches(KeyEventArgs keyEvent) => + public bool Matches(KeyEventArgs? keyEvent) => keyEvent != null && keyEvent.KeyModifiers == KeyModifiers && ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key); diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs index b05d8f30bb..ba909de60f 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Avalonia.Input.Navigation; using Avalonia.VisualTree; @@ -51,7 +50,7 @@ namespace Avalonia.Input // If there's a custom keyboard navigation handler as an ancestor, use that. var custom = (element as Visual)?.FindAncestorOfType(true); - if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) + if (custom is not null && HandlePreCustomNavigation(custom, element, direction, out var ce)) return ce; var result = direction switch @@ -117,32 +116,27 @@ namespace Avalonia.Input NavigationDirection direction, [NotNullWhen(true)] out IInputElement? result) { - if (customHandler != null) + var (handled, next) = customHandler.GetNext(element, direction); + + if (handled) { - var (handled, next) = customHandler.GetNext(element, direction); + if (next is not null) + { + result = next; + return true; + } - if (handled) + var r = direction switch { - if (next != null) - { - result = next; - return true; - } - else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) - { - var r = direction switch - { - NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler), - NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler), - _ => throw new NotSupportedException(), - }; - - if (r is object) - { - result = r; - return true; - } - } + NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler), + NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler), + _ => null + }; + + if (r is not null) + { + result = r; + return true; } } diff --git a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs index d218867cf2..c460ecf3b3 100644 --- a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Avalonia.VisualTree; namespace Avalonia.Input.Navigation @@ -54,8 +52,7 @@ namespace Avalonia.Input.Navigation // Avoid the endless loop here for Cycle groups if (loopStartElement == nextTabElement) break; - if (loopStartElement == null) - loopStartElement = nextTabElement; + loopStartElement ??= nextTabElement; var firstTabElementInside = GetNextTab(null, nextTabElement, true); if (firstTabElementInside != null) @@ -80,12 +77,9 @@ namespace Avalonia.Input.Navigation public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e) { - if (e is IInputElement container) + if (e is IInputElement container && GetLastInTree(container) is { } last) { - var last = GetLastInTree(container); - - if (last is object) - return GetNextTab(last, false); + return GetNextTab(last, false); } return null; @@ -93,11 +87,8 @@ namespace Avalonia.Input.Navigation public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly) { - if (e is null && container is null) - throw new InvalidOperationException("Either 'e' or 'container' must be non-null."); - - if (container is null) - container = GetGroupParent(e!); + container ??= + GetGroupParent(e ?? throw new InvalidOperationException("Either 'e' or 'container' must be non-null.")); KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container); @@ -163,8 +154,7 @@ namespace Avalonia.Input.Navigation // Avoid the endless loop here if (loopStartElement == nextTabElement) break; - if (loopStartElement == null) - loopStartElement = nextTabElement; + loopStartElement ??= nextTabElement; // At this point nextTabElement is TabGroup var lastTabElementInside = GetPrevTab(null, nextTabElement, true); @@ -189,22 +179,18 @@ namespace Avalonia.Input.Navigation public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e) { - if (e is IInputElement container) + if (e is IInputElement container && GetFirstChild(container) is { } first) { - var first = GetFirstChild(container); - - if (first is object) - return GetPrevTab(first, null, false); + return GetPrevTab(first, null, false); } return null; } - private static IInputElement? FocusedElement(IInputElement e) + private static IInputElement? FocusedElement(IInputElement? e) { - var iie = e; // Focus delegation is enabled only if keyboard focus is outside the container - if (iie != null && !iie.IsKeyboardFocusWithin) + if (e != null && !e.IsKeyboardFocusWithin) { var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e); if (focusedElement != null) @@ -229,13 +215,11 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetFirstChild(IInputElement e) { // If the element has a FocusedElement it should be its first child - if (FocusedElement(e) is IInputElement focusedElement) + if (FocusedElement(e) is { } focusedElement) return focusedElement; // Return the first visible element. - var uiElement = e as InputElement; - - if (uiElement is null || IsVisibleAndEnabled(uiElement)) + if (e is not InputElement uiElement || IsVisibleAndEnabled(uiElement)) { if (e is Visual elementAsVisual) { @@ -265,7 +249,7 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetLastChild(IInputElement e) { // If the element has a FocusedElement it should be its last child - if (FocusedElement(e) is IInputElement focusedElement) + if (FocusedElement(e) is { } focusedElement) return focusedElement; // Return the last visible element. @@ -273,9 +257,7 @@ namespace Avalonia.Input.Navigation if (uiElement == null || IsVisibleAndEnabled(uiElement)) { - var elementAsVisual = e as Visual; - - if (elementAsVisual != null) + if (e is Visual elementAsVisual) { var children = elementAsVisual.VisualChildren; var count = children.Count; @@ -322,7 +304,7 @@ namespace Avalonia.Input.Navigation return firstTabElement; } - private static IInputElement? GetLastInTree(IInputElement container) + private static IInputElement GetLastInTree(IInputElement container) { IInputElement? result; IInputElement? c = container; diff --git a/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs b/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs index 74720c0a77..9ab7da3ff7 100644 --- a/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs +++ b/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs @@ -48,7 +48,7 @@ namespace Avalonia.LogicalTree /// The logical. /// If given logical should be included in search. /// First ancestor of given type. - public static T? FindLogicalAncestorOfType(this ILogical logical, bool includeSelf = false) where T : class + public static T? FindLogicalAncestorOfType(this ILogical? logical, bool includeSelf = false) where T : class { if (logical is null) { @@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree /// The logical. /// If given logical should be included in search. /// First descendant of given type. - public static T? FindLogicalDescendantOfType(this ILogical logical, bool includeSelf = false) where T : class + public static T? FindLogicalDescendantOfType(this ILogical? logical, bool includeSelf = false) where T : class { if (logical is null) { @@ -185,7 +185,7 @@ namespace Avalonia.LogicalTree /// True if is an ancestor of ; /// otherwise false. /// - public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target) + public static bool IsLogicalAncestorOf(this ILogical? logical, ILogical? target) { var current = target?.LogicalParent; diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 5470a735b3..ab89177295 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -147,16 +147,11 @@ namespace Avalonia.Media /// The color string. /// The parsed color /// The status of the operation. - public static bool TryParse(string s, out Color color) + public static bool TryParse(string? s, out Color color) { color = default; - if (s is null) - { - return false; - } - - if (s.Length == 0) + if (string.IsNullOrEmpty(s)) { return false; } diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index d295111d72..622181dba0 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -240,7 +240,7 @@ namespace Avalonia.Media /// /// The foreground brush. /// The glyph run. - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun) { _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 481329c20c..b7abda2c61 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -13,14 +13,14 @@ namespace Avalonia.Media public static readonly StyledProperty OpacityProperty = AvaloniaProperty.Register(nameof(Opacity), 1); - public static readonly StyledProperty TransformProperty = - AvaloniaProperty.Register(nameof(Transform)); + public static readonly StyledProperty TransformProperty = + AvaloniaProperty.Register(nameof(Transform)); - public static readonly StyledProperty ClipGeometryProperty = - AvaloniaProperty.Register(nameof(ClipGeometry)); + public static readonly StyledProperty ClipGeometryProperty = + AvaloniaProperty.Register(nameof(ClipGeometry)); - public static readonly StyledProperty OpacityMaskProperty = - AvaloniaProperty.Register(nameof(OpacityMask)); + public static readonly StyledProperty OpacityMaskProperty = + AvaloniaProperty.Register(nameof(OpacityMask)); public static readonly DirectProperty ChildrenProperty = AvaloniaProperty.RegisterDirect( @@ -36,19 +36,19 @@ namespace Avalonia.Media set => SetValue(OpacityProperty, value); } - public Transform Transform + public Transform? Transform { get => GetValue(TransformProperty); set => SetValue(TransformProperty, value); } - public Geometry ClipGeometry + public Geometry? ClipGeometry { get => GetValue(ClipGeometryProperty); set => SetValue(ClipGeometryProperty, value); } - public IBrush OpacityMask + public IBrush? OpacityMask { get => GetValue(OpacityMaskProperty); set => SetValue(OpacityMaskProperty, value); @@ -159,7 +159,7 @@ namespace Avalonia.Media public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) { - if (((brush == null) && (pen == null)) || (geometry == null)) + if ((brush == null) && (pen == null)) { return; } @@ -167,9 +167,9 @@ namespace Avalonia.Media AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry)); } - public void DrawGlyphRun(IBrush foreground, IRef glyphRun) + public void DrawGlyphRun(IBrush? foreground, IRef glyphRun) { - if (foreground == null || glyphRun == null) + if (foreground == null) { return; } @@ -184,7 +184,7 @@ namespace Avalonia.Media AddDrawing(glyphRunDrawing); } - public void DrawLine(IPen pen, Point p1, Point p2) + public void DrawLine(IPen? pen, Point p1, Point p2) { if (pen == null) { diff --git a/src/Avalonia.Base/Media/DrawingImage.cs b/src/Avalonia.Base/Media/DrawingImage.cs index 38ddbdfaed..1b22a1ee69 100644 --- a/src/Avalonia.Base/Media/DrawingImage.cs +++ b/src/Avalonia.Base/Media/DrawingImage.cs @@ -20,8 +20,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty DrawingProperty = - AvaloniaProperty.Register(nameof(Drawing)); + public static readonly StyledProperty DrawingProperty = + AvaloniaProperty.Register(nameof(Drawing)); /// public event EventHandler? Invalidated; @@ -30,7 +30,7 @@ namespace Avalonia.Media /// Gets or sets the drawing content. /// [Content] - public Drawing Drawing + public Drawing? Drawing { get => GetValue(DrawingProperty); set => SetValue(DrawingProperty, value); diff --git a/src/Avalonia.Base/Media/FontFamily.cs b/src/Avalonia.Base/Media/FontFamily.cs index da84861668..f4406bd010 100644 --- a/src/Avalonia.Base/Media/FontFamily.cs +++ b/src/Avalonia.Base/Media/FontFamily.cs @@ -119,7 +119,7 @@ namespace Avalonia.Media case 2: { - var source = segments[0].StartsWith("/") + var source = segments[0].StartsWith("/", StringComparison.Ordinal) ? new Uri(segments[0], UriKind.Relative) : new Uri(segments[0], UriKind.RelativeOrAbsolute); @@ -188,7 +188,7 @@ namespace Avalonia.Media { unchecked { - return ((FamilyNames != null ? FamilyNames.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0); + return (FamilyNames.GetHashCode() * 397) ^ (Key is not null ? Key.GetHashCode() : 0); } } diff --git a/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs index f607c67fed..12bb7e77e7 100644 --- a/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs @@ -41,10 +41,7 @@ namespace Avalonia.Media.Fonts { var hash = (int)2166136261; - if (Source != null) - { - hash = (hash * 16777619) ^ Source.GetHashCode(); - } + hash = (hash * 16777619) ^ Source.GetHashCode(); if (BaseUri != null) { diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index 28757b1a1d..3b63a98720 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -1354,7 +1354,7 @@ namespace Avalonia.Media { var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0); - if (highlightBounds != null) + if (highlightBounds.Count > 0) { foreach (var bound in highlightBounds) { @@ -1365,7 +1365,7 @@ namespace Avalonia.Media // Convert logical units (which extend leftward from the right edge // of the paragraph) to physical units. // - // Note that since rect is in logical units, rect.Right corresponds to + // Note that since rect is in logical units, rect.Right corresponds to // the visual *left* edge of the rectangle in the RTL case. Specifically, // is the distance leftward from the right edge of the formatting rectangle // whose width is the paragraph width passed to FormatLine. @@ -1384,7 +1384,7 @@ namespace Avalonia.Media else { accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union); - } + } } } } diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs index 26cc2c3cab..ac2dce1e42 100644 --- a/src/Avalonia.Base/Media/GeometryDrawing.cs +++ b/src/Avalonia.Base/Media/GeometryDrawing.cs @@ -15,8 +15,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty GeometryProperty = - AvaloniaProperty.Register(nameof(Geometry)); + public static readonly StyledProperty GeometryProperty = + AvaloniaProperty.Register(nameof(Geometry)); /// /// Defines the property. @@ -34,7 +34,7 @@ namespace Avalonia.Media /// Gets or sets the that describes the shape of this . /// [Content] - public Geometry Geometry + public Geometry? Geometry { get => GetValue(GeometryProperty); set => SetValue(GeometryProperty, value); diff --git a/src/Avalonia.Base/Media/GlyphRunDrawing.cs b/src/Avalonia.Base/Media/GlyphRunDrawing.cs index 242b9913fa..06d92fd81c 100644 --- a/src/Avalonia.Base/Media/GlyphRunDrawing.cs +++ b/src/Avalonia.Base/Media/GlyphRunDrawing.cs @@ -2,19 +2,19 @@ { public class GlyphRunDrawing : Drawing { - public static readonly StyledProperty ForegroundProperty = - AvaloniaProperty.Register(nameof(Foreground)); + public static readonly StyledProperty ForegroundProperty = + AvaloniaProperty.Register(nameof(Foreground)); - public static readonly StyledProperty GlyphRunProperty = - AvaloniaProperty.Register(nameof(GlyphRun)); + public static readonly StyledProperty GlyphRunProperty = + AvaloniaProperty.Register(nameof(GlyphRun)); - public IBrush Foreground + public IBrush? Foreground { get => GetValue(ForegroundProperty); set => SetValue(ForegroundProperty, value); } - public GlyphRun GlyphRun + public GlyphRun? GlyphRun { get => GetValue(GlyphRunProperty); set => SetValue(GlyphRunProperty, value); diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 425a3138c3..b4bf6fd217 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -254,7 +254,7 @@ namespace Avalonia.Media /// The HSL color string to parse. /// The parsed . /// True if parsing was successful; otherwise, false. - public static bool TryParse(string s, out HslColor hslColor) + public static bool TryParse(string? s, out HslColor hslColor) { bool prefixMatched = false; diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 9f95b31518..f97457c54d 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -254,7 +254,7 @@ namespace Avalonia.Media /// The HSV color string to parse. /// The parsed . /// True if parsing was successful; otherwise, false. - public static bool TryParse(string s, out HsvColor hsvColor) + public static bool TryParse(string? s, out HsvColor hsvColor) { bool prefixMatched = false; diff --git a/src/Avalonia.Base/Media/IVisualBrush.cs b/src/Avalonia.Base/Media/IVisualBrush.cs index 6662613ff4..a7d3e4da10 100644 --- a/src/Avalonia.Base/Media/IVisualBrush.cs +++ b/src/Avalonia.Base/Media/IVisualBrush.cs @@ -1,5 +1,4 @@ using Avalonia.Metadata; -using Avalonia.VisualTree; namespace Avalonia.Media { @@ -12,6 +11,6 @@ namespace Avalonia.Media /// /// Gets the visual to draw. /// - Visual Visual { get; } + Visual? Visual { get; } } } diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs index 1f53f06955..6dff006045 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs @@ -39,17 +39,8 @@ namespace Avalonia.Media.Immutable { return true; } - else if (other is null) - { - return false; - } - if (Offset != other.Offset) - { - return false; - } - - return SequenceEqual(Dashes, other.Dashes); + return other is not null && Offset == other.Offset && SequenceEqual(_dashes, other.Dashes); } /// @@ -58,30 +49,27 @@ namespace Avalonia.Media.Immutable var hashCode = 717868523; hashCode = hashCode * -1521134295 + Offset.GetHashCode(); - if (_dashes != null) + foreach (var i in _dashes) { - foreach (var i in _dashes) - { - hashCode = hashCode * -1521134295 + i.GetHashCode(); - } + hashCode = hashCode * -1521134295 + i.GetHashCode(); } return hashCode; } - private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList? right) + private static bool SequenceEqual(double[] left, IReadOnlyList? right) { if (ReferenceEquals(left, right)) { return true; } - if (left == null || right == null || left.Count != right.Count) + if (right is null || left.Length != right.Count) { return false; } - for (var c = 0; c < left.Count; c++) + for (var c = 0; c < left.Length; c++) { if (left[c] != right[c]) { diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs index 9b443391c5..0b625080e3 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs @@ -1,5 +1,4 @@ using Avalonia.Media.Imaging; -using Avalonia.VisualTree; namespace Avalonia.Media.Immutable { @@ -31,11 +30,11 @@ namespace Avalonia.Media.Immutable RelativeRect? destinationRect = null, double opacity = 1, ImmutableTransform? transform = null, - RelativePoint transformOrigin = new RelativePoint(), + RelativePoint transformOrigin = default, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, TileMode tileMode = TileMode.None, - Imaging.BitmapInterpolationMode bitmapInterpolationMode = Imaging.BitmapInterpolationMode.Default) + BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) : base( alignmentX, alignmentY, @@ -62,6 +61,6 @@ namespace Avalonia.Media.Immutable } /// - public Visual Visual { get; } + public Visual? Visual { get; } } } diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index dc9e5cb907..b74b7df9c5 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -22,8 +22,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty StrokeProperty = - AvaloniaProperty.Register(nameof(Stroke)); + public static readonly StyledProperty StrokeProperty = + AvaloniaProperty.Register(nameof(Stroke)); /// /// Defines the property. @@ -34,8 +34,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty> StrokeDashArrayProperty = - AvaloniaProperty.Register>(nameof(StrokeDashArray)); + public static readonly StyledProperty?> StrokeDashArrayProperty = + AvaloniaProperty.Register?>(nameof(StrokeDashArray)); /// /// Defines the property. @@ -82,7 +82,7 @@ namespace Avalonia.Media /// /// Gets or sets the that specifies how the is painted. /// - public IBrush Stroke + public IBrush? Stroke { get { return GetValue(StrokeProperty); } set { SetValue(StrokeProperty, value); } @@ -101,7 +101,7 @@ namespace Avalonia.Media /// Gets or sets a collection of values that indicate the pattern of dashes and gaps /// that is used to draw the . /// - public AvaloniaList StrokeDashArray + public AvaloniaList? StrokeDashArray { get { return GetValue(StrokeDashArrayProperty); } set { SetValue(StrokeDashArrayProperty, value); } @@ -220,7 +220,7 @@ namespace Avalonia.Media var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); - if (intersections != null && intersections.Count > 0) + if (intersections.Count > 0) { var last = baselineOrigin.X; var finalPos = last + glyphRun.Size.Width; diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index 1261d233ac..2be3e9a94e 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -1,5 +1,4 @@ using Avalonia.Media.Immutable; -using Avalonia.VisualTree; namespace Avalonia.Media { @@ -11,8 +10,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty VisualProperty = - AvaloniaProperty.Register(nameof(Visual)); + public static readonly StyledProperty VisualProperty = + AvaloniaProperty.Register(nameof(Visual)); static VisualBrush() { @@ -38,7 +37,7 @@ namespace Avalonia.Media /// /// Gets or sets the visual to draw. /// - public Visual Visual + public Visual? Visual { get { return GetValue(VisualProperty); } set { SetValue(VisualProperty, value); } diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index c05c04c22e..8509067cd0 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -49,7 +49,7 @@ namespace Avalonia.Platform /// The stroke pen. /// The first point of the line. /// The second point of the line. - void DrawLine(IPen pen, Point p1, Point p2); + void DrawLine(IPen? pen, Point p1, Point p2); /// /// Draws a geometry. @@ -91,7 +91,7 @@ namespace Avalonia.Platform /// /// The foreground. /// The glyph run. - void DrawGlyphRun(IBrush foreground, IRef glyphRun); + void DrawGlyphRun(IBrush? foreground, IRef glyphRun); /// /// Creates a new that can be used as a render layer diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs index 467cd530fc..d1a803fefb 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs +++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs @@ -19,25 +19,20 @@ internal class AssemblyDescriptor : IAssemblyDescriptor public AssemblyDescriptor(Assembly assembly) { Assembly = assembly; + Resources = assembly.GetManifestResourceNames() + .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); + Name = assembly.GetName().Name; - if (assembly != null) + using var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName); + if (resources != null) { - Resources = assembly.GetManifestResourceNames() - .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); - Name = assembly.GetName().Name; - using (var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName)) - { - if (resources != null) - { - Resources.Remove(Constants.AvaloniaResourceName); + Resources.Remove(Constants.AvaloniaResourceName); - var indexLength = new BinaryReader(resources).ReadInt32(); - var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength)); - var baseOffset = indexLength + 4; - AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor) - new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); - } - } + var indexLength = new BinaryReader(resources).ReadInt32(); + var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength)); + var baseOffset = indexLength + 4; + AvaloniaResources = index.ToDictionary(GetPathRooted, r => (IAssetDescriptor) + new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); } } @@ -45,6 +40,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor public Dictionary? Resources { get; } public Dictionary? AvaloniaResources { get; } public string? Name { get; } + private static string GetPathRooted(AvaloniaResourcesIndexEntry r) => r.Path![0] == '/' ? r.Path : '/' + r.Path; } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index a6db4330a3..455e9ebb5f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase { private readonly CompositionPropertySet _propertySet; - internal CompositionAnimation(Compositor compositor) : base(compositor, null!) + internal CompositionAnimation(Compositor compositor) : base(compositor, null) { _propertySet = new CompositionPropertySet(compositor); } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs index bad3991f43..1500e88abe 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.Composition.Animations public void Remove(CompositionAnimation value) => Animations.Remove(value); public void RemoveAll() => Animations.Clear(); - public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!) + public CompositionAnimationGroup(Compositor compositor) : base(compositor, null) { } } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs index 72be4edd07..d9adf261f8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations { private Dictionary _inner = new Dictionary(); private IDictionary _innerface; - internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!) + internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null) { _innerface = _inner; } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs index ab4329df62..bfe70d593d 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Avalonia.Rendering.Composition.Server; using Avalonia.Threading; @@ -7,7 +6,7 @@ namespace Avalonia.Rendering.Composition; public class CompositionDrawingSurface : CompositionSurface { - internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server; + internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server!; internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server)) { } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs index 50332926ad..8c21b534db 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs @@ -22,7 +22,7 @@ namespace Avalonia.Rendering.Composition public ImplicitAnimationCollection? ImplicitAnimations { get; set; } private protected InlineDictionary PendingAnimations; - internal CompositionObject(Compositor compositor, ServerObject server) + internal CompositionObject(Compositor compositor, ServerObject? server) { Compositor = compositor; Server = server; @@ -32,7 +32,7 @@ namespace Avalonia.Rendering.Composition /// The associated Compositor /// public Compositor Compositor { get; } - internal ServerObject Server { get; } + internal ServerObject? Server { get; } public bool IsDisposed { get; private set; } private bool _registeredForSerialization; diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs index 7d794af9a2..efd89951bb 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition private readonly Dictionary _variants = new Dictionary(); private readonly Dictionary _objects = new Dictionary(); - internal CompositionPropertySet(Compositor compositor) : base(compositor, null!) + internal CompositionPropertySet(Compositor compositor) : base(compositor, null) { } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index 05488a558f..b75d080cfd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -88,8 +88,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW } /// - public void DrawLine(IPen pen, Point p1, Point p2) + public void DrawLine(IPen? pen, Point p1, Point p2) { + if (pen is null) + { + return; + } + var next = NextDrawAs(); if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) @@ -159,8 +164,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW public object? GetFeature(Type t) => null; /// - public void DrawGlyphRun(IBrush foreground, IRef glyphRun) + public void DrawGlyphRun(IBrush? foreground, IRef glyphRun) { + if (foreground is null) + { + return; + } + var next = NextDrawAs(); if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs index 560ee05c10..b15da5d05d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs @@ -165,8 +165,6 @@ namespace Avalonia.Rendering.Composition.Expressions public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) { - if (context.ForeignFunctionInterface == null) - return default; var args = new List(); foreach (var expr in Parameters) args.Add(expr.Evaluate(ref context)); diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs index 9086c59aad..f268364b54 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Avalonia.Rendering.Composition.Server; // Special license applies License.md diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index c58beebe7f..50df8bd32b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -66,7 +66,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect); } - public void DrawLine(IPen pen, Point p1, Point p2) + public void DrawLine(IPen? pen, Point p1, Point p2) { _impl.DrawLine(pen, p1, p2); } @@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.DrawEllipse(brush, pen, rect); } - public void DrawGlyphRun(IBrush foreground, IRef glyphRun) + public void DrawGlyphRun(IBrush? foreground, IRef glyphRun) { _impl.DrawGlyphRun(foreground, glyphRun); } diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index c67ac7057d..8e5dc38317 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -48,8 +48,10 @@ namespace Avalonia.Rendering /// void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) { - var visual = brush.Visual; - Render(new DrawingContext(context), visual, visual.Bounds); + if (brush.Visual is { } visual) + { + Render(new DrawingContext(context), visual, visual.Bounds); + } } internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs index 12b67105e9..82f8fc2d56 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -80,11 +80,8 @@ namespace Avalonia.Rendering.SceneGraph { p *= Transform.Invert(); - if (Material != null) - { - var rect = Rect.Rect; - return rect.ContainsExclusive(p); - } + var rect = Rect.Rect; + return rect.ContainsExclusive(p); } return false; diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 3c44dd63ce..fafafabd82 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -212,7 +212,7 @@ namespace Avalonia.Utilities var toTypeConverter = TypeDescriptor.GetConverter(toUnderl); - if (toTypeConverter.CanConvertFrom(from) == true) + if (toTypeConverter.CanConvertFrom(from)) { result = toTypeConverter.ConvertFrom(null, culture, value); return true; @@ -220,7 +220,7 @@ namespace Avalonia.Utilities var fromTypeConverter = TypeDescriptor.GetConverter(from); - if (fromTypeConverter.CanConvertTo(toUnderl) == true) + if (fromTypeConverter.CanConvertTo(toUnderl)) { result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl); return true; @@ -329,7 +329,7 @@ namespace Avalonia.Utilities } [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] - public static T ConvertImplicit(object value) + public static T ConvertImplicit(object? value) { if (TryConvertImplicit(typeof(T), value, out var result)) { @@ -369,11 +369,6 @@ namespace Avalonia.Utilities /// public static bool IsNumeric(Type type) { - if (type == null) - { - return false; - } - var underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) diff --git a/src/Avalonia.Base/Utilities/WeakEvent.cs b/src/Avalonia.Base/Utilities/WeakEvent.cs index e72606bf70..237a491615 100644 --- a/src/Avalonia.Base/Utilities/WeakEvent.cs +++ b/src/Avalonia.Base/Utilities/WeakEvent.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Threading; @@ -15,7 +11,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event { private readonly Func, Action> _subscribe; - readonly ConditionalWeakTable _subscriptions = new(); + private readonly ConditionalWeakTable _subscriptions = new(); internal WeakEvent( Action> subscribe, @@ -51,56 +47,6 @@ public class WeakEvent : WeakEvent where TEventArgs : Event private readonly WeakEvent _ev; private readonly TSender _target; private readonly Action _compact; - - struct Entry - { - WeakReference>? _reference; - int _hashCode; - - public Entry(IWeakEventSubscriber r) - { - if (r == null) - { - _reference = null; - _hashCode = 0; - return; - } - - _hashCode = r.GetHashCode(); - _reference = new WeakReference>(r); - } - - public bool IsEmpty - { - get - { - if (_reference == null) - return true; - if (_reference.TryGetTarget(out _)) - return false; - _reference = null; - return true; - } - } - - public bool TryGetTarget([MaybeNullWhen(false)]out IWeakEventSubscriber target) - { - if (_reference == null) - { - target = null!; - return false; - } - return _reference.TryGetTarget(out target); - } - - public bool Equals(IWeakEventSubscriber r) - { - if (_reference == null || r.GetHashCode() != _hashCode) - return false; - return _reference.TryGetTarget(out var target) && target == r; - } - } - private readonly Action _unsubscribe; private readonly WeakHashList> _list = new(); private bool _compactScheduled; @@ -114,7 +60,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event _unsubscribe = ev._subscribe(target, OnEvent); } - void Destroy() + private void Destroy() { if(_destroyed) return; @@ -134,15 +80,15 @@ public class WeakEvent : WeakEvent where TEventArgs : Event ScheduleCompact(); } - void ScheduleCompact() + private void ScheduleCompact() { if(_compactScheduled || _destroyed) return; _compactScheduled = true; Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background); } - - void Compact() + + private void Compact() { if(!_compactScheduled) return; @@ -152,7 +98,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event Destroy(); } - void OnEvent(object? sender, TEventArgs eventArgs) + private void OnEvent(object? sender, TEventArgs eventArgs) { var alive = _list.GetAlive(); if(alive == null) @@ -196,4 +142,4 @@ public class WeakEvent return () => unsubscribe(s, handler); }); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index 020ba7a6d9..ef143144e6 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -60,8 +60,7 @@ namespace Avalonia.Utilities private static class SubscriptionTypeStorage where TArgs : EventArgs where TSubscriber : class { - public static readonly ConditionalWeakTable> Subscribers - = new ConditionalWeakTable>(); + public static readonly ConditionalWeakTable> Subscribers = new(); } private class SubscriptionDic : Dictionary> @@ -69,8 +68,7 @@ namespace Avalonia.Utilities { } - private static readonly Dictionary> Accessors - = new Dictionary>(); + private static readonly Dictionary> s_accessors = new(); private class Subscription where T : EventArgs where TSubscriber : class { @@ -81,18 +79,17 @@ namespace Avalonia.Utilities private readonly Delegate _delegate; private Descriptor[] _data = new Descriptor[2]; - private int _count = 0; + private int _count; - delegate void CallerDelegate(TSubscriber s, object sender, T args); - - struct Descriptor + private delegate void CallerDelegate(TSubscriber s, object? sender, T args); + + private struct Descriptor { - public WeakReference Subscriber; - public CallerDelegate Caller; + public WeakReference? Subscriber; + public CallerDelegate? Caller; } - private static Dictionary s_Callers = - new Dictionary(); + private static readonly Dictionary s_callers = new(); public Subscription(SubscriptionDic sdic, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType, @@ -101,8 +98,8 @@ namespace Avalonia.Utilities _sdic = sdic; _target = target; _eventName = eventName; - if (!Accessors.TryGetValue(targetType, out var evDic)) - Accessors[targetType] = evDic = new Dictionary(); + if (!s_accessors.TryGetValue(targetType, out var evDic)) + s_accessors[targetType] = evDic = new Dictionary(); if (evDic.TryGetValue(eventName, out var info)) { @@ -123,12 +120,12 @@ namespace Avalonia.Utilities var del = new Action(OnEvent); _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target); - _info.AddMethod!.Invoke(target, new[] { _delegate }); + _info.AddMethod!.Invoke(target, new object?[] { _delegate }); } - void Destroy() + private void Destroy() { - _info.RemoveMethod!.Invoke(_target, new[] { _delegate }); + _info.RemoveMethod!.Invoke(_target, new object?[] { _delegate }); _sdic.Remove(_eventName); } @@ -146,8 +143,8 @@ namespace Avalonia.Utilities MethodInfo method = s.Method; var subscriber = (TSubscriber)s.Target!; - if (!s_Callers.TryGetValue(method, out var caller)) - s_Callers[method] = caller = + if (!s_callers.TryGetValue(method, out var caller)) + s_callers[method] = caller = (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method); _data[_count] = new Descriptor { @@ -178,7 +175,7 @@ namespace Avalonia.Utilities } } - void Compact(bool preventDestroy = false) + private void Compact(bool preventDestroy = false) { int empty = -1; for (int c = 0; c < _count; c++) @@ -206,15 +203,15 @@ namespace Avalonia.Utilities Destroy(); } - void OnEvent(object sender, T eventArgs) + private void OnEvent(object? sender, T eventArgs) { var needCompact = false; - for(var c=0; c<_count; c++) + for (var c = 0; c < _count; c++) { - var r = _data[c].Subscriber; + var r = _data[c].Subscriber!; if (r.TryGetTarget(out var sub)) { - _data[c].Caller(sub, sender, eventArgs); + _data[c].Caller!(sub, sender, eventArgs); } else needCompact = true; diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index e6d7492c51..58e84f29d8 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -483,17 +483,13 @@ namespace Avalonia parent.HasNonUniformZIndexChildren = true; var visualChildren = VisualChildren; + var visualChildrenCount = visualChildren.Count; - if (visualChildren != null) + for (var i = 0; i < visualChildrenCount; i++) { - var visualChildrenCount = visualChildren.Count; - - for (var i = 0; i < visualChildrenCount; i++) + if (visualChildren[i] is { } child) { - if (visualChildren[i] is Visual child) - { - child.OnAttachedToVisualTreeCore(e); - } + child.OnAttachedToVisualTreeCore(e); } } } @@ -543,17 +539,13 @@ namespace Avalonia e.Root?.Renderer?.AddDirty(this); var visualChildren = VisualChildren; + var visualChildrenCount = visualChildren.Count; - if (visualChildren != null) + for (var i = 0; i < visualChildrenCount; i++) { - var visualChildrenCount = visualChildren.Count; - - for (var i = 0; i < visualChildrenCount; i++) + if (visualChildren[i] is { } child) { - if (visualChildren[i] is Visual child) - { - child.OnDetachedFromVisualTreeCore(e); - } + child.OnDetachedFromVisualTreeCore(e); } } } diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs index b58db3b276..9e38c6e7f2 100644 --- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs @@ -46,7 +46,7 @@ namespace Avalonia.VisualTree Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); var result = 0; - v = v?.VisualParent; + v = v.VisualParent; while (v != null) { @@ -64,17 +64,13 @@ namespace Avalonia.VisualTree /// The first visual. /// The second visual. /// The common ancestor, or null if not found. - public static Visual? FindCommonVisualAncestor(this Visual visual, Visual target) + public static Visual? FindCommonVisualAncestor(this Visual? visual, Visual? target) { - Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); - - if (target is null) + if (visual is null || target is null) { return null; } - Visual? t = target; - void GoUpwards(ref Visual? node, int count) { for (int i = 0; i < count; ++i) @@ -83,6 +79,9 @@ namespace Avalonia.VisualTree } } + Visual? v = visual; + Visual? t = target; + // We want to find lowest node first, then make sure that both nodes are at the same height. // By doing that we can sometimes find out that other node is our lowest common ancestor. var firstHeight = CalculateDistanceFromRoot(v); @@ -144,7 +143,7 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First ancestor of given type. - public static T? FindAncestorOfType(this Visual visual, bool includeSelf = false) where T : class + public static T? FindAncestorOfType(this Visual? visual, bool includeSelf = false) where T : class { if (visual is null) { @@ -173,7 +172,7 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First descendant of given type. - public static T? FindDescendantOfType(this Visual visual, bool includeSelf = false) where T : class + public static T? FindDescendantOfType(this Visual? visual, bool includeSelf = false) where T : class { if (visual is null) { @@ -392,7 +391,7 @@ namespace Avalonia.VisualTree /// True if is an ancestor of ; /// otherwise false. /// - public static bool IsVisualAncestorOf(this Visual visual, Visual target) + public static bool IsVisualAncestorOf(this Visual? visual, Visual? target) { Visual? current = target?.VisualParent; diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 225e846390..68466fe381 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -141,9 +141,7 @@ namespace Avalonia.Headless } public IReadOnlyList GetIntersections(float lowerBound, float upperBound) - { - return null; - } + => Array.Empty(); } class HeadlessGeometryStub : IGeometryImpl diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index dcb20d2a44..ba646c64ee 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -208,6 +208,12 @@ namespace Avalonia.Skia public void DrawLine(IPen pen, Point p1, Point p2) { CheckLease(); + + if (pen is null) + { + return; + } + using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) { if (paint.Paint is object) @@ -495,6 +501,12 @@ namespace Avalonia.Skia public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { CheckLease(); + + if (foreground is null) + { + return; + } + using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.Item; diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs index 24b8fc04b3..446db47d92 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Platform; using SharpDX.DirectWrite; @@ -25,8 +26,6 @@ namespace Avalonia.Direct2D1.Media } public IReadOnlyList GetIntersections(float lowerBound, float upperBound) - { - return null; - } + => Array.Empty(); } } diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs index 477f34565f..0319803a5e 100644 --- a/tests/Avalonia.UnitTests/MockGlyphRun.cs +++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Media.TextFormatting; using Avalonia.Platform; @@ -24,12 +25,9 @@ namespace Avalonia.UnitTests public void Dispose() { - } public IReadOnlyList GetIntersections(float lowerBound, float upperBound) - { - return null; - } + => Array.Empty(); } } From d99b795504cedec99428e2038bf1df2408215122 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 7 Feb 2023 11:54:32 +0800 Subject: [PATCH 25/76] fix: fix ListBoxItem generation. --- src/Avalonia.Controls/ListBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 8b1a307182..80d1677c2f 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -104,6 +104,7 @@ namespace Avalonia.Controls public void UnselectAll() => Selection.Clear(); protected internal override Control CreateContainerForItemOverride() => new ListBoxItem(); + protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is ListBoxItem; /// protected override void OnGotFocus(GotFocusEventArgs e) From 6dae36ead9858385e710a117c8a2bc4b002e960c Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 7 Feb 2023 14:01:39 +0000 Subject: [PATCH 26/76] Revert "Only set pointer events to handled in button if click is triggered" --- src/Avalonia.Controls/Button.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 9627f200df..1ec6f8dabc 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -394,10 +394,10 @@ namespace Avalonia.Controls if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { IsPressed = true; + e.Handled = true; if (ClickMode == ClickMode.Press) { - e.Handled = true; OnClick(); } } @@ -411,11 +411,11 @@ namespace Avalonia.Controls if (IsPressed && e.InitialPressMouseButton == MouseButton.Left) { IsPressed = false; + e.Handled = true; if (ClickMode == ClickMode.Release && this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c))) { - e.Handled = true; OnClick(); } } From e1138f2cb6a393802b235a073d28e85a64690ffe Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 7 Feb 2023 16:53:49 +0100 Subject: [PATCH 27/76] Nullability fixes for Avalonia.Controls --- .../Input/Platform/IClipboard.cs | 6 +- src/Avalonia.Base/Media/Color.cs | 2 +- .../Platform/Internal/AssemblyDescriptor.cs | 5 +- .../ClassicDesktopStyleApplicationLifetime.cs | 28 +- ...IClassicDesktopStyleApplicationLifetime.cs | 5 +- .../AutoCompleteBox/AutoCompleteBox.cs | 2 +- .../Automation/AutomationProperties.cs | 282 +++++------------- src/Avalonia.Controls/Calendar/Calendar.cs | 18 +- .../CalendarBlackoutDatesCollection.cs | 14 +- .../Calendar/CalendarItem.cs | 112 +++---- .../Calendar/DateTimeHelper.cs | 33 +- .../CalendarDatePicker/CalendarDatePicker.cs | 20 +- src/Avalonia.Controls/Chrome/TitleBar.cs | 40 +-- .../MenuScrollingVisibilityConverter.cs | 1 - src/Avalonia.Controls/DefinitionBase.cs | 90 +++--- src/Avalonia.Controls/DockPanel.cs | 9 +- src/Avalonia.Controls/Documents/Inline.cs | 9 +- src/Avalonia.Controls/Documents/Span.cs | 14 +- src/Avalonia.Controls/Grid.cs | 230 +++++++------- src/Avalonia.Controls/Image.cs | 7 +- src/Avalonia.Controls/ItemsControl.cs | 12 +- .../LayoutTransformControl.cs | 20 +- src/Avalonia.Controls/MaskedTextBox.cs | 12 +- src/Avalonia.Controls/NativeControlHost.cs | 22 +- src/Avalonia.Controls/NativeMenu.Export.cs | 20 +- .../Primitives/AdornerLayer.cs | 12 +- src/Avalonia.Controls/Primitives/Popup.cs | 12 +- .../Primitives/SelectingItemsControl.cs | 31 +- .../Primitives/TemplatedControl.cs | 7 +- .../Primitives/TextSearch.cs | 8 +- src/Avalonia.Controls/Primitives/Track.cs | 19 +- .../Primitives/VisualLayerManager.cs | 16 +- src/Avalonia.Controls/RelativePanel.cs | 7 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 44 ++- src/Avalonia.Controls/Slider.cs | 13 +- src/Avalonia.Controls/StackPanel.cs | 9 +- src/Avalonia.Controls/TickBar.cs | 18 +- src/Avalonia.Controls/TrayIcon.cs | 9 +- src/Avalonia.Controls/TreeView.cs | 16 +- src/Avalonia.Controls/Viewbox.cs | 36 +-- src/Avalonia.Controls/WrapPanel.cs | 107 +++---- src/Browser/Avalonia.Browser/ClipboardImpl.cs | 10 +- 42 files changed, 580 insertions(+), 807 deletions(-) diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs index bf2a5a8602..3de352fc4f 100644 --- a/src/Avalonia.Base/Input/Platform/IClipboard.cs +++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs @@ -6,9 +6,9 @@ namespace Avalonia.Input.Platform [NotClientImplementable] public interface IClipboard { - Task GetTextAsync(); + Task GetTextAsync(); - Task SetTextAsync(string text); + Task SetTextAsync(string? text); Task ClearAsync(); @@ -16,6 +16,6 @@ namespace Avalonia.Input.Platform Task GetFormatsAsync(); - Task GetDataAsync(string format); + Task GetDataAsync(string format); } } diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index ab89177295..74e70b2a14 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -331,7 +331,7 @@ namespace Avalonia.Media /// /// Parses the given string representing a CSS color value into a new . /// - private static bool TryParseCssFormat(string s, out Color color) + private static bool TryParseCssFormat(string? s, out Color color) { bool prefixMatched = false; diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs index d1a803fefb..6a577c204c 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs +++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -18,7 +19,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor { public AssemblyDescriptor(Assembly assembly) { - Assembly = assembly; + Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); Resources = assembly.GetManifestResourceNames() .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); Name = assembly.GetName().Name; diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index fde401fb01..ada0b94124 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -17,32 +16,34 @@ namespace Avalonia.Controls.ApplicationLifetimes private int _exitCode; private CancellationTokenSource? _cts; private bool _isShuttingDown; - private HashSet _windows = new HashSet(); + private readonly HashSet _windows = new(); + + private static ClassicDesktopStyleApplicationLifetime? s_activeLifetime; - private static ClassicDesktopStyleApplicationLifetime? _activeLifetime; static ClassicDesktopStyleApplicationLifetime() { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); - Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); + Window.WindowClosedEvent.AddClassHandler(typeof(Window), OnWindowClosed); } - private static void WindowClosedEvent(object? sender, RoutedEventArgs e) + private static void OnWindowClosed(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Remove((Window)sender!); - _activeLifetime?.HandleWindowClosed((Window)sender!); + var window = (Window)sender!; + s_activeLifetime?._windows.Remove(window); + s_activeLifetime?.HandleWindowClosed(window); } private static void OnWindowOpened(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Add((Window)sender!); + s_activeLifetime?._windows.Add((Window)sender!); } public ClassicDesktopStyleApplicationLifetime() { - if (_activeLifetime != null) + if (s_activeLifetime != null) throw new InvalidOperationException( "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); - _activeLifetime = this; + s_activeLifetime = this; } /// @@ -65,9 +66,10 @@ namespace Avalonia.Controls.ApplicationLifetimes /// public Window? MainWindow { get; set; } + /// public IReadOnlyList Windows => _windows.ToArray(); - private void HandleWindowClosed(Window window) + private void HandleWindowClosed(Window? window) { if (window == null) return; @@ -130,8 +132,8 @@ namespace Avalonia.Controls.ApplicationLifetimes public void Dispose() { - if (_activeLifetime == this) - _activeLifetime = null; + if (s_activeLifetime == this) + s_activeLifetime = null; } private bool DoShutdown( diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 22b5f8236d..b9a372f935 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -40,7 +40,10 @@ namespace Avalonia.Controls.ApplicationLifetimes /// The main window. /// Window? MainWindow { get; set; } - + + /// + /// Gets the list of all open windows in the application. + /// IReadOnlyList Windows { get; } /// diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 55649660f7..9a949e31d4 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -1711,7 +1711,7 @@ namespace Avalonia.Controls /// The predicate to use for the partial or /// exact match. /// Returns the object or null. - private object? TryGetMatch(string? searchText, AvaloniaList view, AutoCompleteFilterPredicate? predicate) + private object? TryGetMatch(string? searchText, AvaloniaList? view, AutoCompleteFilterPredicate? predicate) { if (predicate is null) return null; diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs index 35f94722ce..3ea9c170ff 100644 --- a/src/Avalonia.Controls/Automation/AutomationProperties.cs +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -38,8 +38,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.AcceleratorKey attached property. /// - public static readonly AttachedProperty AcceleratorKeyProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AcceleratorKeyProperty = + AvaloniaProperty.RegisterAttached( "AcceleratorKey", typeof(AutomationProperties)); @@ -54,16 +54,16 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.AccessKey attached property /// - public static readonly AttachedProperty AccessKeyProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AccessKeyProperty = + AvaloniaProperty.RegisterAttached( "AccessKey", typeof(AutomationProperties)); /// /// Defines the AutomationProperties.AutomationId attached property. /// - public static readonly AttachedProperty AutomationIdProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AutomationIdProperty = + AvaloniaProperty.RegisterAttached( "AutomationId", typeof(AutomationProperties)); @@ -78,8 +78,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.HelpText attached property. /// - public static readonly AttachedProperty HelpTextProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty HelpTextProperty = + AvaloniaProperty.RegisterAttached( "HelpText", typeof(AutomationProperties)); @@ -122,16 +122,16 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.ItemStatus attached property. /// - public static readonly AttachedProperty ItemStatusProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty ItemStatusProperty = + AvaloniaProperty.RegisterAttached( "ItemStatus", typeof(AutomationProperties)); /// /// Defines the AutomationProperties.ItemType attached property. /// - public static readonly AttachedProperty ItemTypeProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty ItemTypeProperty = + AvaloniaProperty.RegisterAttached( "ItemType", typeof(AutomationProperties)); @@ -155,8 +155,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.Name attached attached property. /// - public static readonly AttachedProperty NameProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty NameProperty = + AvaloniaProperty.RegisterAttached( "Name", typeof(AutomationProperties)); @@ -193,25 +193,17 @@ namespace Avalonia.Automation /// public static void SetAcceleratorKey(StyledElement element, string value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AcceleratorKeyProperty, value); } /// /// Helper for reading AcceleratorKey property from a StyledElement. /// - public static string GetAcceleratorKey(StyledElement element) + public static string? GetAcceleratorKey(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(AcceleratorKeyProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(AcceleratorKeyProperty); } /// @@ -219,11 +211,7 @@ namespace Avalonia.Automation /// public static void SetAccessibilityView(StyledElement element, AccessibilityView value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AccessibilityViewProperty, value); } @@ -232,11 +220,7 @@ namespace Avalonia.Automation /// public static AccessibilityView GetAccessibilityView(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(AccessibilityViewProperty); } @@ -245,50 +229,34 @@ namespace Avalonia.Automation /// public static void SetAccessKey(StyledElement element, string value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AccessKeyProperty, value); } /// /// Helper for reading AccessKey property from a StyledElement. /// - public static string GetAccessKey(StyledElement element) + public static string? GetAccessKey(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(AccessKeyProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(AccessKeyProperty); } /// /// Helper for setting AutomationId property on a StyledElement. /// - public static void SetAutomationId(StyledElement element, string value) + public static void SetAutomationId(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AutomationIdProperty, value); } /// /// Helper for reading AutomationId property from a StyledElement. /// - public static string GetAutomationId(StyledElement element) + public static string? GetAutomationId(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(AutomationIdProperty); } @@ -297,11 +265,7 @@ namespace Avalonia.Automation /// public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ControlTypeOverrideProperty, value); } @@ -310,38 +274,26 @@ namespace Avalonia.Automation /// public static AutomationControlType? GetControlTypeOverride(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(ControlTypeOverrideProperty); } /// /// Helper for setting HelpText property on a StyledElement. /// - public static void SetHelpText(StyledElement element, string value) + public static void SetHelpText(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(HelpTextProperty, value); } /// /// Helper for reading HelpText property from a StyledElement. /// - public static string GetHelpText(StyledElement element) + public static string? GetHelpText(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(HelpTextProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(HelpTextProperty); } /// @@ -349,11 +301,7 @@ namespace Avalonia.Automation /// public static void SetIsColumnHeader(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsColumnHeaderProperty, value); } @@ -362,12 +310,8 @@ namespace Avalonia.Automation /// public static bool GetIsColumnHeader(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsColumnHeaderProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsColumnHeaderProperty); } /// @@ -375,11 +319,7 @@ namespace Avalonia.Automation /// public static void SetIsRequiredForForm(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsRequiredForFormProperty, value); } @@ -388,12 +328,8 @@ namespace Avalonia.Automation /// public static bool GetIsRequiredForForm(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsRequiredForFormProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsRequiredForFormProperty); } /// @@ -401,12 +337,8 @@ namespace Avalonia.Automation /// public static bool GetIsRowHeader(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsRowHeaderProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsRowHeaderProperty); } /// @@ -414,11 +346,7 @@ namespace Avalonia.Automation /// public static void SetIsRowHeader(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsRowHeaderProperty, value); } @@ -427,11 +355,7 @@ namespace Avalonia.Automation /// public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsOffscreenBehaviorProperty, value); } @@ -440,64 +364,44 @@ namespace Avalonia.Automation /// public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsOffscreenBehaviorProperty); } /// /// Helper for setting ItemStatus property on a StyledElement. /// - public static void SetItemStatus(StyledElement element, string value) + public static void SetItemStatus(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ItemStatusProperty, value); } /// /// Helper for reading ItemStatus property from a StyledElement. /// - public static string GetItemStatus(StyledElement element) + public static string? GetItemStatus(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(ItemStatusProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(ItemStatusProperty); } /// /// Helper for setting ItemType property on a StyledElement. /// - public static void SetItemType(StyledElement element, string value) + public static void SetItemType(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ItemTypeProperty, value); } /// /// Helper for reading ItemType property from a StyledElement. /// - public static string GetItemType(StyledElement element) + public static string? GetItemType(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(ItemTypeProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(ItemTypeProperty); } /// @@ -505,11 +409,7 @@ namespace Avalonia.Automation /// public static void SetLabeledBy(StyledElement element, Control value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(LabeledByProperty, value); } @@ -518,11 +418,7 @@ namespace Avalonia.Automation /// public static Control GetLabeledBy(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(LabeledByProperty); } @@ -531,11 +427,7 @@ namespace Avalonia.Automation /// public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(LiveSettingProperty, value); } @@ -544,38 +436,26 @@ namespace Avalonia.Automation /// public static AutomationLiveSetting GetLiveSetting(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(LiveSettingProperty); } /// /// Helper for setting Name property on a StyledElement. /// - public static void SetName(StyledElement element, string value) + public static void SetName(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(NameProperty, value); } /// /// Helper for reading Name property from a StyledElement. /// - public static string GetName(StyledElement element) + public static string? GetName(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(NameProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(NameProperty); } /// @@ -583,11 +463,7 @@ namespace Avalonia.Automation /// public static void SetPositionInSet(StyledElement element, int value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(PositionInSetProperty, value); } @@ -596,12 +472,8 @@ namespace Avalonia.Automation /// public static int GetPositionInSet(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((int)element.GetValue(PositionInSetProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(PositionInSetProperty); } /// @@ -609,11 +481,7 @@ namespace Avalonia.Automation /// public static void SetSizeOfSet(StyledElement element, int value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(SizeOfSetProperty, value); } @@ -622,12 +490,8 @@ namespace Avalonia.Automation /// public static int GetSizeOfSet(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((int)element.GetValue(SizeOfSetProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(SizeOfSetProperty); } } } diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 9c88bae5f6..3300292857 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -237,11 +237,11 @@ namespace Avalonia.Controls private DateTime _selectedYear; private DateTime _displayDate = DateTime.Today; - private DateTime? _displayDateStart = null; - private DateTime? _displayDateEnd = null; + private DateTime? _displayDateStart; + private DateTime? _displayDateEnd; private bool _isShiftPressed; - private bool _displayDateIsChanging = false; + private bool _displayDateIsChanging; internal CalendarDayButton? FocusButton { get; set; } internal CalendarButton? FocusCalendarButton { get; set; } @@ -291,7 +291,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek"); + throw new ArgumentOutOfRangeException(nameof(e), "Invalid DayOfWeek"); } } @@ -346,10 +346,10 @@ namespace Avalonia.Controls } } - public static readonly StyledProperty HeaderBackgroundProperty = - AvaloniaProperty.Register(nameof(HeaderBackground)); + public static readonly StyledProperty HeaderBackgroundProperty = + AvaloniaProperty.Register(nameof(HeaderBackground)); - public IBrush HeaderBackground + public IBrush? HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } set { SetValue(HeaderBackgroundProperty, value); } @@ -478,7 +478,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode"); + throw new ArgumentOutOfRangeException(nameof(e), "Invalid SelectionMode"); } } @@ -574,7 +574,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "SelectedDate value is not valid."); + throw new ArgumentOutOfRangeException(nameof(e), "SelectedDate value is not valid."); } } else diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs index fe8b616e02..8fb9b66f3d 100644 --- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Primitives /// /// The Calendar whose dates this object represents. /// - private Calendar _owner; + private readonly Calendar _owner; /// /// Initializes a new instance of the @@ -79,13 +79,13 @@ namespace Avalonia.Controls.Primitives if (DateTime.Compare(end, start) > -1) { - rangeStart = DateTimeHelper.DiscardTime(start).Value; - rangeEnd = DateTimeHelper.DiscardTime(end).Value; + rangeStart = DateTimeHelper.DiscardTime(start); + rangeEnd = DateTimeHelper.DiscardTime(end); } else { - rangeStart = DateTimeHelper.DiscardTime(end).Value; - rangeEnd = DateTimeHelper.DiscardTime(start).Value; + rangeStart = DateTimeHelper.DiscardTime(end); + rangeEnd = DateTimeHelper.DiscardTime(start); } int count = Count; @@ -144,7 +144,7 @@ namespace Avalonia.Controls.Primitives if (!IsValid(item)) { - throw new ArgumentOutOfRangeException("Value is not valid."); + throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid."); } base.InsertItem(index, item); @@ -186,7 +186,7 @@ namespace Avalonia.Controls.Primitives if (!IsValid(item)) { - throw new ArgumentOutOfRangeException("Value is not valid."); + throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid."); } base.SetItem(index, item); diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 032f452111..3d436b4485 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -44,30 +44,30 @@ namespace Avalonia.Controls.Primitives private ITemplate? _dayTitleTemplate; private DateTime _currentMonth; - private bool _isMouseLeftButtonDown = false; - private bool _isMouseLeftButtonDownYearView = false; - private bool _isControlPressed = false; + private bool _isMouseLeftButtonDown; + private bool _isMouseLeftButtonDownYearView; + private bool _isControlPressed; - private System.Globalization.Calendar _calendar = new System.Globalization.GregorianCalendar(); - - private PointerPressedEventArgs? _downEventArg; - private PointerPressedEventArgs? _downEventArgYearView; + private readonly System.Globalization.Calendar _calendar = new GregorianCalendar(); internal Calendar? Owner { get; set; } internal CalendarDayButton? CurrentButton { get; set; } - public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); - public IBrush HeaderBackground + public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); + + public IBrush? HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } set { SetValue(HeaderBackgroundProperty, value); } } + public static readonly DirectProperty?> DayTitleTemplateProperty = AvaloniaProperty.RegisterDirect?>( nameof(DayTitleTemplate), o => o.DayTitleTemplate, (o,v) => o.DayTitleTemplate = v, defaultBindingMode: BindingMode.OneTime); + public ITemplate? DayTitleTemplate { get { return _dayTitleTemplate; } @@ -178,7 +178,7 @@ namespace Avalonia.Controls.Primitives { if (_dayTitleTemplate != null) { - var cell = (Control) _dayTitleTemplate.Build(); + var cell = _dayTitleTemplate.Build(); cell.DataContext = string.Empty; cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.ColumnProperty, i); @@ -308,16 +308,13 @@ namespace Avalonia.Controls.Primitives for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++) { var daytitle = MonthView!.Children[childIndex]; - if (daytitle != null) + if (Owner != null) { - if (Owner != null) - { - daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek]; - } - else - { - daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek]; - } + daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek]; + } + else + { + daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek]; } } } @@ -527,7 +524,7 @@ namespace Avalonia.Controls.Primitives childButton.Content = dateToAdd.Day.ToString(DateTimeHelper.GetCurrentDateFormat()); childButton.DataContext = dateToAdd; - if (DateTime.Compare((DateTime)DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0) + if (DateTime.Compare(DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0) { // Since we are sure DisplayDate is not equal to // DateTime.MaxValue, it is safe to use AddDays @@ -587,7 +584,7 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - _currentMonth = (DateTime)Owner.SelectedMonth; + _currentMonth = Owner.SelectedMonth; } else { @@ -676,7 +673,7 @@ namespace Avalonia.Controls.Primitives if (Owner != null) { selectedYear = Owner.SelectedYear; - _currentMonth = (DateTime)Owner.SelectedMonth; + _currentMonth = Owner.SelectedMonth; } else { @@ -696,9 +693,9 @@ namespace Avalonia.Controls.Primitives SetYearButtons(decade, decadeEnd); } } - internal void UpdateYearViewSelection(CalendarButton calendarButton) + internal void UpdateYearViewSelection(CalendarButton? calendarButton) { - if (Owner != null && calendarButton != null && calendarButton.DataContext != null) + if (Owner != null && calendarButton?.DataContext is DateTime selectedDate) { Owner.FocusCalendarButton!.IsCalendarButtonFocused = false; Owner.FocusCalendarButton = calendarButton; @@ -706,11 +703,11 @@ namespace Avalonia.Controls.Primitives if (Owner.DisplayMode == CalendarMode.Year) { - Owner.SelectedMonth = (DateTime)calendarButton.DataContext; + Owner.SelectedMonth = selectedDate; } else { - Owner.SelectedYear = (DateTime)calendarButton.DataContext; + Owner.SelectedYear = selectedDate; } } } @@ -719,7 +716,7 @@ namespace Avalonia.Controls.Primitives { int year; int count = -1; - foreach (object child in YearView!.Children) + foreach (var child in YearView!.Children) { CalendarButton childButton = (CalendarButton)child; year = decade + count; @@ -859,7 +856,8 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - if (_isMouseLeftButtonDown && sender is CalendarDayButton b && b.IsEnabled && !b.IsBlackout) + if (_isMouseLeftButtonDown + && sender is CalendarDayButton { IsEnabled: true, IsBlackout: false, DataContext: DateTime selectedDate } b) { // Update the states of all buttons to be selected starting // from HoverStart to b @@ -867,7 +865,6 @@ namespace Avalonia.Controls.Primitives { case CalendarSelectionMode.SingleDate: { - DateTime selectedDate = (DateTime)b.DataContext!; Owner.CalendarDatePickerDisplayDateFlag = true; if (Owner.SelectedDates.Count == 0) { @@ -882,10 +879,9 @@ namespace Avalonia.Controls.Primitives case CalendarSelectionMode.SingleRange: case CalendarSelectionMode.MultipleRange: { - Debug.Assert(b.DataContext != null, "The DataContext should not be null!"); Owner.UnHighlightDays(); Owner.HoverEndIndex = b.Index; - Owner.HoverEnd = (DateTime?)b.DataContext; + Owner.HoverEnd = selectedDate; // Update the States of the buttons Owner.HighlightDays(); return; @@ -904,22 +900,14 @@ namespace Avalonia.Controls.Primitives Owner.Focus(); } - bool ctrl, shift; - CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out ctrl, out shift); - CalendarDayButton b = (CalendarDayButton)sender!; + CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out var ctrl, out var shift); - if (b != null) + if (sender is CalendarDayButton b) { _isControlPressed = ctrl; - if (b.IsEnabled && !b.IsBlackout) + if (b.IsEnabled && !b.IsBlackout && b.DataContext is DateTime selectedDate) { - DateTime selectedDate = (DateTime)b.DataContext!; _isMouseLeftButtonDown = true; - // null check is added for unit tests - if (e != null) - { - _downEventArg = e; - } switch (Owner.SelectionMode) { @@ -1010,12 +998,12 @@ namespace Avalonia.Controls.Primitives } } } - private void AddSelection(CalendarDayButton b) + private void AddSelection(CalendarDayButton b, DateTime selectedDate) { if (Owner != null) { Owner.HoverEndIndex = b.Index; - Owner.HoverEnd = (DateTime)b.DataContext!; + Owner.HoverEnd = selectedDate; if (Owner.HoverEnd != null && Owner.HoverStart != null) { @@ -1025,7 +1013,7 @@ namespace Avalonia.Controls.Primitives // SelectionMode Owner.IsMouseSelection = true; Owner.SelectedDates.AddRange(Owner.HoverStart.Value, Owner.HoverEnd.Value); - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); } } } @@ -1039,11 +1027,11 @@ namespace Avalonia.Controls.Primitives Owner.OnDayButtonMouseUp(e); } _isMouseLeftButtonDown = false; - if (b != null && b.DataContext != null) + if (b != null && b.DataContext is DateTime selectedDate) { if (Owner.SelectionMode == CalendarSelectionMode.None || Owner.SelectionMode == CalendarSelectionMode.SingleDate) { - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); return; } if (Owner.HoverStart.HasValue) @@ -1058,14 +1046,14 @@ namespace Avalonia.Controls.Primitives Owner.RemovedItems.Add(item); } Owner.SelectedDates.ClearInternal(); - AddSelection(b); + AddSelection(b, selectedDate); return; } case CalendarSelectionMode.MultipleRange: { // add the selection (either single day or // SingleRange day) - AddSelection(b); + AddSelection(b, selectedDate); return; } } @@ -1076,7 +1064,7 @@ namespace Avalonia.Controls.Primitives // be able to switch months if (b.IsInactive && b.IsBlackout) { - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); } } } @@ -1095,9 +1083,9 @@ namespace Avalonia.Controls.Primitives Owner.HoverStart = null; _isMouseLeftButtonDown = false; b.IsSelected = false; - if (b.DataContext != null) + if (b.DataContext is DateTime selectedDate) { - Owner.SelectedDates.Remove((DateTime)b.DataContext); + Owner.SelectedDates.Remove(selectedDate); } } } @@ -1107,35 +1095,26 @@ namespace Avalonia.Controls.Primitives private void Month_CalendarButtonMouseDown(object? sender, PointerPressedEventArgs e) { - CalendarButton b = (CalendarButton)sender!; - _isMouseLeftButtonDownYearView = true; - if (e != null) - { - _downEventArgYearView = e; - } - - UpdateYearViewSelection(b); + UpdateYearViewSelection(sender as CalendarButton); } internal void Month_CalendarButtonMouseUp(object? sender, PointerReleasedEventArgs e) { _isMouseLeftButtonDownYearView = false; - if (Owner != null) + if (Owner != null && (sender as CalendarButton)?.DataContext is DateTime newMonth) { - DateTime newmonth = (DateTime)((CalendarButton)sender!).DataContext!; - if (Owner.DisplayMode == CalendarMode.Year) { - Owner.DisplayDate = newmonth; + Owner.DisplayDate = newMonth; Owner.DisplayMode = CalendarMode.Month; } else { Debug.Assert(Owner.DisplayMode == CalendarMode.Decade, "The owning Calendar should be in decade mode!"); - Owner.SelectedMonth = newmonth; + Owner.SelectedMonth = newMonth; Owner.DisplayMode = CalendarMode.Year; } } @@ -1145,8 +1124,7 @@ namespace Avalonia.Controls.Primitives { if (_isMouseLeftButtonDownYearView) { - CalendarButton b = (CalendarButton)sender!; - UpdateYearViewSelection(b); + UpdateYearViewSelection(sender as CalendarButton); } } diff --git a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs index bfff03a926..570f05cfe8 100644 --- a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs +++ b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls public static int CompareDays(DateTime dt1, DateTime dt2) { - return DateTime.Compare(DiscardTime(dt1).Value, DiscardTime(dt2).Value); + return DateTime.Compare(DiscardTime(dt1), DiscardTime(dt2)); } public static int CompareYearMonth(DateTime dt1, DateTime dt2) @@ -71,14 +71,9 @@ namespace Avalonia.Controls return new DateTime(d.Year, d.Month, 1, 0, 0, 0); } - [return: NotNullIfNotNull("d")] - public static DateTime? DiscardTime(DateTime? d) + public static DateTime DiscardTime(DateTime d) { - if (d == null) - { - return null; - } - return d.Value.Date; + return d.Date; } public static int EndOfDecade(DateTime date) @@ -127,28 +122,14 @@ namespace Avalonia.Controls public static string ToYearMonthPatternString(DateTime date) { - string result = string.Empty; - DateTimeFormatInfo format = GetCurrentDateFormat(); - - if (format != null) - { - result = date.ToString(format.YearMonthPattern, format); - } - - return result; + var format = GetCurrentDateFormat(); + return date.ToString(format.YearMonthPattern, format); } public static string ToYearString(DateTime date) { - string result = string.Empty; - DateTimeFormatInfo format = GetCurrentDateFormat(); - - if (format != null) - { - result = date.Year.ToString(format); - } - - return result; + var format = GetCurrentDateFormat(); + return date.Year.ToString(format); } } } diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index b17648f5bb..869bdeabea 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -51,11 +51,11 @@ namespace Avalonia.Controls private bool _isDropDownOpen; private DateTime? _selectedDate; private string? _text; - private bool _suspendTextChangeHandler = false; - private bool _isPopupClosing = false; - private bool _ignoreButtonClick = false; - private bool _isFlyoutOpen = false; - private bool _isPressed = false; + private bool _suspendTextChangeHandler; + private bool _isPopupClosing; + private bool _ignoreButtonClick; + private bool _isFlyoutOpen; + private bool _isPressed; /// /// Occurs when the drop-down @@ -185,7 +185,7 @@ namespace Avalonia.Controls { _textBox.KeyDown += TextBox_KeyDown; _textBox.GotFocus += TextBox_GotFocus; - _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged()); + _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(_ => TextBox_TextChanged()); if(SelectedDate.HasValue) { @@ -292,7 +292,7 @@ namespace Avalonia.Controls // Text else if (change.Property == TextProperty) { - var (oldValue, newValue) = change.GetOldAndNewValue(); + var (_, newValue) = change.GetOldAndNewValue(); if (!_suspendTextChangeHandler) { @@ -595,9 +595,9 @@ namespace Avalonia.Controls private void Calendar_KeyDown(object? sender, KeyEventArgs e) { - Calendar? c = sender as Calendar ?? throw new ArgumentException("Sender must be Calendar.", nameof(sender)); - - if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month) + if (!e.Handled + && sender is Calendar { DisplayMode: CalendarMode.Month } + && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape)) { Focus(); IsDropDownOpen = false; diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 47b0bb6e2d..368c9d4c2f 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -17,28 +17,26 @@ namespace Avalonia.Controls.Chrome private void UpdateSize(Window window) { - if (window != null) + Margin = new Thickness( + window.OffScreenMargin.Left, + window.OffScreenMargin.Top, + window.OffScreenMargin.Right, + window.OffScreenMargin.Bottom); + + if (window.WindowState != WindowState.FullScreen) { - Margin = new Thickness( - window.OffScreenMargin.Left, - window.OffScreenMargin.Top, - window.OffScreenMargin.Right, - window.OffScreenMargin.Bottom); + Height = window.WindowDecorationMargin.Top; - if (window.WindowState != WindowState.FullScreen) + if (_captionButtons != null) { - Height = window.WindowDecorationMargin.Top; - - if (_captionButtons != null) - { - _captionButtons.Height = Height; - } + _captionButtons.Height = Height; } - - IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } + + IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -55,6 +53,7 @@ namespace Avalonia.Controls.Chrome } } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -64,13 +63,13 @@ namespace Avalonia.Controls.Chrome _disposables = new CompositeDisposable(6) { window.GetObservable(Window.WindowDecorationMarginProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.OffScreenMarginProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.ExtendClientAreaChromeHintsProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.WindowStateProperty) .Subscribe(x => { @@ -80,11 +79,12 @@ namespace Avalonia.Controls.Chrome PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); }), window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty) - .Subscribe(x => UpdateSize(window)) + .Subscribe(_ => UpdateSize(window)) }; } } + /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs index 9d859a753a..18d668e9a4 100644 --- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs +++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs @@ -14,7 +14,6 @@ namespace Avalonia.Controls.Converters public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { if (parameter == null || - values == null || values.Count != 4 || !(values[0] is ScrollBarVisibility visibility) || !(values[1] is double offset) || diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 5c35a09f1c..eb587fb157 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -21,9 +21,9 @@ namespace Avalonia.Controls /// /// SharedSizeGroup property. /// - public string SharedSizeGroup + public string? SharedSizeGroup { - get { return (string)GetValue(SharedSizeGroupProperty); } + get { return GetValue(SharedSizeGroupProperty); } set { SetValue(SharedSizeGroupProperty, value); } } @@ -32,20 +32,15 @@ namespace Avalonia.Controls /// internal void OnEnterParentTree() { - this.InheritanceParent = Parent; + InheritanceParent = Parent; if (_sharedState == null) { // start with getting SharedSizeGroup value. // this property is NOT inherited which should result in better overall perf. - string sharedSizeGroupId = SharedSizeGroup; - if (sharedSizeGroupId != null) + if (SharedSizeGroup is { } sharedSizeGroupId && PrivateSharedSizeScope is { } privateSharedSizeScope) { - SharedSizeScope? privateSharedSizeScope = PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - _sharedState.AddMember(this); - } + _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + _sharedState.AddMember(this); } } @@ -321,13 +316,12 @@ namespace Avalonia.Controls return ((_flags & flags) == flags); } - private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, + AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase)d; - if (definition.Parent != null) { - string sharedSizeGroupId = (string)e.NewValue!; + string? sharedSizeGroupId = e.NewValue.Value; if (definition._sharedState != null) { @@ -337,16 +331,14 @@ namespace Avalonia.Controls definition._sharedState = null; } - if ((definition._sharedState == null) && (sharedSizeGroupId != null)) + if (definition._sharedState == null + && sharedSizeGroupId != null + && definition.PrivateSharedSizeScope is { } privateSharedSizeScope) { - SharedSizeScope? privateSharedSizeScope = definition.PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - definition._sharedState.AddMember(definition); - } + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + definition._sharedState.AddMember(definition); } } } @@ -357,17 +349,15 @@ namespace Avalonia.Controls /// b) contains only letters, digits and underscore ('_'). /// c) does not start with a digit. /// - private static bool SharedSizeGroupPropertyValueValid(string value) + private static bool SharedSizeGroupPropertyValueValid(string? id) { // null is default value - if (value == null) + if (id == null) { return true; } - string id = (string)value; - - if (!string.IsNullOrEmpty(id)) + if (id.Length > 0) { int i = -1; while (++i < id.Length) @@ -397,14 +387,11 @@ namespace Avalonia.Controls /// existing scope just left. In both cases if the DefinitionBase object is already registered /// in SharedSizeState, it should un-register and register itself in a new one. /// - private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + private static void OnPrivateSharedSizeScopePropertyChanged(DefinitionBase definition, + AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase)d; - if (definition.Parent != null) { - SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue!; - if (definition._sharedState != null) { // if definition is already registered And shared size scope is changing, @@ -413,16 +400,14 @@ namespace Avalonia.Controls definition._sharedState = null; } - if ((definition._sharedState == null) && (privateSharedSizeScope != null)) + if (definition._sharedState == null + && e.NewValue.Value is { } privateSharedSizeScope + && definition.SharedSizeGroup is { } sharedSizeGroup) { - string sharedSizeGroup = definition.SharedSizeGroup; - if (sharedSizeGroup != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup); - definition._sharedState.AddMember(definition); - } + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroup); + definition._sharedState.AddMember(definition); } } } @@ -432,7 +417,7 @@ namespace Avalonia.Controls /// private SharedSizeScope? PrivateSharedSizeScope { - get { return (SharedSizeScope?)GetValue(PrivateSharedSizeScopeProperty); } + get { return GetValue(PrivateSharedSizeScopeProperty); } } /// @@ -465,7 +450,7 @@ namespace Avalonia.Controls private SharedSizeState? _sharedState; // reference to shared state object this instance is registered with - [System.Flags] + [Flags] private enum Flags : byte { // @@ -520,11 +505,10 @@ namespace Avalonia.Controls /// internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) { - Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); _sharedSizeScope = sharedSizeScope; _sharedSizeGroupId = sharedSizeGroupId; _registry = new List(); - _layoutUpdated = new EventHandler(OnLayoutUpdated); + _layoutUpdated = OnLayoutUpdated; _broadcastInvalidation = true; } @@ -568,7 +552,7 @@ namespace Avalonia.Controls { for (int i = 0, count = _registry.Count; i < count; ++i) { - Grid parentGrid = (Grid)(_registry[i].Parent!); + Grid parentGrid = _registry[i].Parent!; parentGrid.Invalidate(); } _broadcastInvalidation = false; @@ -703,7 +687,7 @@ namespace Avalonia.Controls // measure is invalid - it used the old shared size, // which is larger than d's (possibly changed) minSize measureIsValid = (definitionBase.LayoutWasUpdated && - MathUtilities.GreaterThanOrClose(definitionBase._minSize, this.MinSize)); + MathUtilities.GreaterThanOrClose(definitionBase._minSize, MinSize)); } if(!measureIsValid) @@ -786,8 +770,8 @@ namespace Avalonia.Controls /// /// /// - public static readonly AttachedProperty SharedSizeGroupProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty SharedSizeGroupProperty = + AvaloniaProperty.RegisterAttached( "SharedSizeGroup", validate: SharedSizeGroupPropertyValueValid); @@ -796,8 +780,8 @@ namespace Avalonia.Controls /// static DefinitionBase() { - SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); } /// diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs index 3e3ed509b5..1a0cf1644a 100644 --- a/src/Avalonia.Controls/DockPanel.cs +++ b/src/Avalonia.Controls/DockPanel.cs @@ -101,9 +101,6 @@ namespace Avalonia.Controls Size childConstraint; // Contains the suggested input constraint for this child. Size childDesiredSize; // Contains the return size from child measure. - if (child == null) - { continue; } - // Child constraint is the remaining size; this is total size minus size consumed by previous children. childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth), Math.Max(0.0, constraint.Height - accumulatedHeight)); @@ -122,7 +119,7 @@ namespace Avalonia.Controls // will deal with computing our minimum size (parentSize) due to that accumulation. // Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does // not accumulate: Width for Top/Bottom, Height for Left/Right. - switch (DockPanel.GetDock((Control)child)) + switch (GetDock(child)) { case Dock.Left: case Dock.Right: @@ -164,8 +161,6 @@ namespace Avalonia.Controls for (int i = 0; i < totalChildrenCount; ++i) { var child = children[i]; - if (child == null) - { continue; } Size childDesiredSize = child.DesiredSize; Rect rcChild = new Rect( @@ -176,7 +171,7 @@ namespace Avalonia.Controls if (i < nonFillChildrenCount) { - switch (DockPanel.GetDock((Control)child)) + switch (GetDock(child)) { case Dock.Left: accumulatedLeft += childDesiredSize.Width; diff --git a/src/Avalonia.Controls/Documents/Inline.cs b/src/Avalonia.Controls/Documents/Inline.cs index 47581e87f1..23b806583e 100644 --- a/src/Avalonia.Controls/Documents/Inline.cs +++ b/src/Avalonia.Controls/Documents/Inline.cs @@ -13,8 +13,8 @@ namespace Avalonia.Controls.Documents /// /// AvaloniaProperty for property. /// - public static readonly StyledProperty TextDecorationsProperty = - AvaloniaProperty.Register( + public static readonly StyledProperty TextDecorationsProperty = + AvaloniaProperty.Register( nameof(TextDecorations)); /// @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Documents /// /// The TextDecorations property specifies decorations that are added to the text of an element. /// - public TextDecorationCollection TextDecorations + public TextDecorationCollection? TextDecorations { get { return GetValue(TextDecorationsProperty); } set { SetValue(TextDecorationsProperty, value); } @@ -83,7 +83,8 @@ namespace Avalonia.Controls.Documents return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize, textDecorations, Foreground, background, BaselineAlignment); } - + + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index a7a702ceae..d3565cbdd5 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text; using Avalonia.Media.TextFormatting; @@ -51,6 +52,7 @@ namespace Avalonia.Controls.Documents } } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -68,26 +70,26 @@ namespace Avalonia.Controls.Documents { base.OnInlineHostChanged(oldValue, newValue); - if (Inlines is not null) - { - Inlines.InlineHost = newValue; - } + Inlines.InlineHost = newValue; } private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) { + void OnInlinesInvalidated(object? sender, EventArgs e) + => InlineHost?.Invalidate(); + if (oldValue is not null) { oldValue.LogicalChildren = null; oldValue.InlineHost = null; - oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate(); + oldValue.Invalidated -= OnInlinesInvalidated; } if (newValue is not null) { newValue.LogicalChildren = LogicalChildren; newValue.InlineHost = InlineHost; - newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); + newValue.Invalidated += OnInlinesInvalidated; } } } diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 7737fdac2e..2501440ff2 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -164,20 +164,21 @@ namespace Avalonia.Controls /// /// Returns a ColumnDefinitions of column definitions. /// + [MemberNotNull(nameof(_extData))] public ColumnDefinitions ColumnDefinitions { get { - if (_data == null) { _data = new ExtendedData(); } - if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } + if (_extData == null) { _extData = new ExtendedData(); } + if (_extData.ColumnDefinitions == null) { _extData.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } - return (_data.ColumnDefinitions); + return (_extData.ColumnDefinitions); } set { - if (_data == null) { _data = new ExtendedData(); } - _data.ColumnDefinitions = value; - _data.ColumnDefinitions.Parent = this; + if (_extData == null) { _extData = new ExtendedData(); } + _extData.ColumnDefinitions = value; + _extData.ColumnDefinitions.Parent = this; InvalidateMeasure(); } } @@ -185,20 +186,21 @@ namespace Avalonia.Controls /// /// Returns a RowDefinitions of row definitions. /// + [MemberNotNull(nameof(_extData))] public RowDefinitions RowDefinitions { get { - if (_data == null) { _data = new ExtendedData(); } - if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions() { Parent = this }; } + if (_extData == null) { _extData = new ExtendedData(); } + if (_extData.RowDefinitions == null) { _extData.RowDefinitions = new RowDefinitions() { Parent = this }; } - return (_data.RowDefinitions); + return (_extData.RowDefinitions); } set { - if (_data == null) { _data = new ExtendedData(); } - _data.RowDefinitions = value; - _data.RowDefinitions.Parent = this; + if (_extData == null) { _extData = new ExtendedData(); } + _extData.RowDefinitions = value; + _extData.RowDefinitions.Parent = this; InvalidateMeasure(); } } @@ -211,7 +213,7 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size constraint) { Size gridDesiredSize; - ExtendedData extData = ExtData; + var extData = _extData; try { @@ -221,17 +223,14 @@ namespace Avalonia.Controls if (extData == null) { gridDesiredSize = new Size(); - var children = this.Children; + var children = Children; for (int i = 0, count = children.Count; i < count; ++i) { var child = children[i]; - if (child != null) - { - child.Measure(constraint); - gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), - Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); - } + child.Measure(constraint); + gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); } } else @@ -512,17 +511,14 @@ namespace Avalonia.Controls { ArrangeOverrideInProgress = true; - if (_data == null) + if (_extData is null) { - var children = this.Children; + var children = Children; for (int i = 0, count = children.Count; i < count; ++i) { var child = children[i]; - if (child != null) - { - child.Arrange(new Rect(arrangeSize)); - } + child.Arrange(new Rect(arrangeSize)); } } else @@ -532,15 +528,11 @@ namespace Avalonia.Controls SetFinalSize(DefinitionsU, arrangeSize.Width, true); SetFinalSize(DefinitionsV, arrangeSize.Height, false); - var children = this.Children; + var children = Children; for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) { var cell = children[currentCell]; - if (cell == null) - { - continue; - } int columnIndex = PrivateCells[currentCell].ColumnIndex; int rowIndex = PrivateCells[currentCell].RowIndex; @@ -599,7 +591,7 @@ namespace Avalonia.Controls { double value = 0.0; - Debug.Assert(_data != null); + Debug.Assert(_extData != null); // actual value calculations require structure to be up-to-date if (!ColumnDefinitionsDirty) @@ -621,7 +613,7 @@ namespace Avalonia.Controls { double value = 0.0; - Debug.Assert(_data != null); + Debug.Assert(_extData != null); // actual value calculations require structure to be up-to-date if (!RowDefinitionsDirty) @@ -654,18 +646,20 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsUStructure bit flag. /// + [MemberNotNull(nameof(_extData))] internal bool ColumnDefinitionsDirty { - get => ColumnDefinitions?.IsDirty ?? false; + get => ColumnDefinitions.IsDirty; set => ColumnDefinitions.IsDirty = value; } /// /// Convenience accessor to ValidDefinitionsVStructure bit flag. /// + [MemberNotNull(nameof(_extData))] internal bool RowDefinitionsDirty { - get => RowDefinitions?.IsDirty ?? false; + get => RowDefinitions.IsDirty; set => RowDefinitions.IsDirty = value; } @@ -686,8 +680,10 @@ namespace Avalonia.Controls /// private void ValidateCellsCore() { - var children = this.Children; - ExtendedData extData = ExtData; + Debug.Assert(_extData is not null); + + var children = Children; + var extData = _extData!; extData.CellCachesCollection = new CellCache[children.Count]; extData.CellGroup1 = int.MaxValue; @@ -702,10 +698,6 @@ namespace Avalonia.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { var child = children[i]; - if (child == null) - { - continue; - } CellCache cell = new CellCache(); @@ -713,19 +705,19 @@ namespace Avalonia.Controls // Read indices from the corresponding properties: // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn((Control)child), DefinitionsU.Count - 1); + cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Count - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow((Control)child), DefinitionsV.Count - 1); + cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Count - 1); // Read span properties: // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan((Control)child), DefinitionsU.Count - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Count - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan((Control)child), DefinitionsV.Count - cell.RowIndex); + cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Count - cell.RowIndex); Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count); @@ -792,7 +784,7 @@ namespace Avalonia.Controls { if (ColumnDefinitionsDirty) { - ExtendedData extData = ExtData; + var extData = _extData; if (extData.ColumnDefinitions == null) { @@ -818,7 +810,7 @@ namespace Avalonia.Controls ColumnDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Count > 0); + Debug.Assert(_extData is { DefinitionsU.Count: > 0 }); } /// @@ -833,7 +825,7 @@ namespace Avalonia.Controls { if (RowDefinitionsDirty) { - ExtendedData extData = ExtData; + var extData = _extData; if (extData.RowDefinitions == null) { @@ -859,7 +851,7 @@ namespace Avalonia.Controls RowDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Count > 0); + Debug.Assert(_extData is { DefinitionsV.Count: > 0 }); } /// @@ -965,8 +957,7 @@ namespace Avalonia.Controls bool ignoreDesiredSizeU, bool forceInfinityV) { - bool unusedHasDesiredSizeUChanged; - MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out unusedHasDesiredSizeUChanged); + MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out _); } /// @@ -994,7 +985,7 @@ namespace Avalonia.Controls return; } - var children = this.Children; + var children = Children; Hashtable? spanStore = null; bool ignoreDesiredSizeV = forceInfinityV; @@ -1101,8 +1092,6 @@ namespace Avalonia.Controls int cell, bool forceInfinityV) { - - double cellMeasureWidth; double cellMeasureHeight; @@ -1144,15 +1133,9 @@ namespace Avalonia.Controls } - var child = this.Children[cell]; - if (child != null) - { - Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); - child.Measure(childConstraint); - } - - - + var child = Children[cell]; + Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); + child.Measure(childConstraint); } /// @@ -1230,7 +1213,7 @@ namespace Avalonia.Controls // avoid processing when asked to distribute "0" if (!MathUtilities.IsZero(requestedSize)) { - DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting + DefinitionBase?[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting int end = start + count; int autoDefinitionsCount = 0; double rangeMinSize = 0; @@ -1288,20 +1271,24 @@ namespace Avalonia.Controls Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(tempDefinition.UserSize.IsAuto); // adjust sizeToDistribute value by subtracting auto definition min size - sizeToDistribute -= (tempDefinitions[i].MinSize); + sizeToDistribute -= (tempDefinition.MinSize); } for (; i < count; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(!tempDefinition.UserSize.IsAuto); - double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); - if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } + double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinition.PreferredSize); + if (newMinSize > tempDefinition.MinSize) { tempDefinition.UpdateMinSize(newMinSize); } sizeToDistribute -= newMinSize; } @@ -1325,24 +1312,28 @@ namespace Avalonia.Controls Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(!tempDefinition.UserSize.IsAuto); - double preferredSize = tempDefinitions[i].PreferredSize; + double preferredSize = tempDefinition.PreferredSize; double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache)); + sizeToDistribute -= (tempDefinition.MinSize - preferredSize); } for (; i < count; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(tempDefinition.UserSize.IsAuto); - double preferredSize = tempDefinitions[i].MinSize; + double preferredSize = tempDefinition.MinSize; double newMinSize = preferredSize + sizeToDistribute / (count - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache)); + sizeToDistribute -= (tempDefinition.MinSize - preferredSize); } // sanity check: requested size must all be distributed @@ -1376,8 +1367,10 @@ namespace Avalonia.Controls for (int i = 0; i < count; ++i) { - double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; - tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); + var tempDefinition = tempDefinitions[i]!; + + double deltaSize = (maxMaxSize - tempDefinition.SizeCache) * sizeToDistribute / totalRemainingSize; + tempDefinition.UpdateMinSize(tempDefinition.SizeCache + deltaSize); } } else @@ -1388,7 +1381,7 @@ namespace Avalonia.Controls // for (int i = 0; i < count; ++i) { - tempDefinitions[i].UpdateMinSize(equalSize); + tempDefinitions[i]!.UpdateMinSize(equalSize); } } } @@ -1429,7 +1422,7 @@ namespace Avalonia.Controls double availableSize) { int defCount = definitions.Count; - DefinitionBase[] tempDefinitions = TempDefinitions; + DefinitionBase?[] tempDefinitions = TempDefinitions; int minCount = 0, maxCount = 0; double takenSize = 0; double totalStarWeight = 0.0; @@ -1560,8 +1553,8 @@ namespace Avalonia.Controls remainingStarWeight = totalStarWeight - takenStarWeight; } - double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; - double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; + double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1]!.MeasureSize : Double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1]!.SizeCache : -1.0; // choose the def with larger ratio to the current proportion ("max discrepancy") double proportion = remainingStarWeight / remainingAvailableSize; @@ -1579,13 +1572,13 @@ namespace Avalonia.Controls double resolvedSize; if (chooseMin == true) { - resolvedDef = tempDefinitions[minCount - 1]; + resolvedDef = tempDefinitions[minCount - 1]!; resolvedSize = resolvedDef.MinSize; --minCount; } else { - resolvedDef = tempDefinitions[defCount + maxCount - 1]; + resolvedDef = tempDefinitions[defCount + maxCount - 1]!; resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); --maxCount; } @@ -1603,12 +1596,12 @@ namespace Avalonia.Controls // advance to the next candidate defs, removing ones that have been resolved. // Both counts are advanced, as a def might appear in both lists. - while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) + while (minCount > 0 && tempDefinitions[minCount - 1]!.MeasureSize < 0.0) { --minCount; tempDefinitions[minCount] = null!; } - while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) + while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1]!.MeasureSize < 0.0) { --maxCount; tempDefinitions[defCount + maxCount] = null!; @@ -1637,8 +1630,7 @@ namespace Avalonia.Controls // resolved as 'min'. Their allocation can be increased to make up the gap. for (int i = minCount; i < minCountPhase2; ++i) { - DefinitionBase def = tempDefinitions[i]; - if (def != null) + if (tempDefinitions[i] is { } def) { def.MeasureSize = 1.0; // mark as 'not yet resolved' ++starCount; @@ -1653,8 +1645,7 @@ namespace Avalonia.Controls // resolved as 'max'. Their allocation can be decreased to make up the gap. for (int i = maxCount; i < maxCountPhase2; ++i) { - DefinitionBase def = tempDefinitions[defCount + i]; - if (def != null) + if (tempDefinitions[defCount + i] is { } def) { def.MeasureSize = 1.0; // mark as 'not yet resolved' ++starCount; @@ -1695,7 +1686,7 @@ namespace Avalonia.Controls totalStarWeight = 0.0; for (int i = 0; i < starCount; ++i) { - DefinitionBase def = tempDefinitions[i]; + DefinitionBase def = tempDefinitions[i]!; totalStarWeight += def.MeasureSize; def.SizeCache = totalStarWeight; } @@ -1703,7 +1694,7 @@ namespace Avalonia.Controls // resolve the defs, in decreasing order of weight for (int i = starCount - 1; i >= 0; --i) { - DefinitionBase def = tempDefinitions[i]; + DefinitionBase def = tempDefinitions[i]!; double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; // min and max should have no effect by now, but just in case... @@ -2095,7 +2086,7 @@ namespace Avalonia.Controls { // DpiScale dpiScale = GetDpi(); // double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; - var dpi = (VisualRoot as Layout.ILayoutRoot)?.LayoutScaling ?? 1.0; + var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; double[] roundingErrors = RoundingErrors; double roundedTakenSize = 0.0; @@ -2302,8 +2293,7 @@ namespace Avalonia.Controls /// private void SetValid() { - ExtendedData extData = ExtData; - if (extData != null) + if (_extData is { } extData) { // for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); // for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); @@ -2330,12 +2320,12 @@ namespace Avalonia.Controls if (ShowGridLines && (_gridLinesRenderer == null)) { _gridLinesRenderer = new GridLinesRenderer(); - this.VisualChildren.Add(_gridLinesRenderer); + VisualChildren.Add(_gridLinesRenderer); } if ((!ShowGridLines) && (_gridLinesRenderer != null)) { - this.VisualChildren.Add(_gridLinesRenderer); + VisualChildren.Add(_gridLinesRenderer); _gridLinesRenderer = null; } @@ -2364,7 +2354,7 @@ namespace Avalonia.Controls { Grid grid = (Grid)d; - if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway + if (grid._extData != null // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) { grid.InvalidateVisual(); @@ -2375,13 +2365,11 @@ namespace Avalonia.Controls private static void OnCellAttachedPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { - Visual? child = d as Visual; - - if (child != null) + if (d is Visual child) { Grid? grid = child.GetVisualParent(); if (grid != null - && grid.ExtData != null + && grid._extData != null && grid.ListenToNotifications) { grid.CellsStructureDirty = true; @@ -2427,7 +2415,7 @@ namespace Avalonia.Controls /// private IReadOnlyList DefinitionsU { - get { return (ExtData.DefinitionsU!); } + get { return _extData!.DefinitionsU!; } } /// @@ -2435,17 +2423,19 @@ namespace Avalonia.Controls /// private IReadOnlyList DefinitionsV { - get { return (ExtData.DefinitionsV!); } + get { return _extData!.DefinitionsV!; } } /// /// Helper accessor to layout time array of definitions. /// - private DefinitionBase[] TempDefinitions + private DefinitionBase?[] TempDefinitions { get { - ExtendedData extData = ExtData; + Debug.Assert(_extData is not null); + + var extData = _extData!; int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count) * 2; if (extData.TempDefinitions == null @@ -2516,7 +2506,7 @@ namespace Avalonia.Controls /// private CellCache[] PrivateCells { - get { return (ExtData.CellCachesCollection!); } + get { return _extData!.CellCachesCollection!; } } /// @@ -2582,18 +2572,10 @@ namespace Avalonia.Controls set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } } - /// - /// Returns reference to extended data bag. - /// - private ExtendedData ExtData - { - get { return (_data!); } - } - /// /// Returns *-weight, adjusted for scale computed during Phase 1 /// - static double StarWeight(DefinitionBase def, double scale) + private static double StarWeight(DefinitionBase def, double scale) { if (scale < 0.0) { @@ -2609,17 +2591,17 @@ namespace Avalonia.Controls } // Extended data instantiated on demand, for non-trivial case handling only - private ExtendedData? _data; + private ExtendedData? _extData; // Grid validity / property caches dirtiness flags private Flags _flags; private GridLinesRenderer? _gridLinesRenderer; // Keeps track of definition indices. - int[]? _definitionIndices; + private int[]? _definitionIndices; // Stores unrounded values and rounding errors during layout rounding. - double[]? _roundingErrors; + private double[]? _roundingErrors; // 5 is an arbitrary constant chosen to end the measure loop private const int c_layoutLoopMaxCount = 5; @@ -2645,14 +2627,14 @@ namespace Avalonia.Controls internal int CellGroup2; // index of the first cell in second cell group internal int CellGroup3; // index of the first cell in third cell group internal int CellGroup4; // index of the first cell in forth cell group - internal DefinitionBase[]? TempDefinitions; // temporary array used during layout for various purposes + internal DefinitionBase?[]? TempDefinitions; // temporary array used during layout for various purposes // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length) } /// /// Grid validity / property caches dirtiness flags /// - [System.Flags] + [Flags] private enum Flags { // @@ -2768,7 +2750,7 @@ namespace Avalonia.Controls /// /// LayoutTimeSizeType is used internally and reflects layout-time size type. /// - [System.Flags] + [Flags] internal enum LayoutTimeSizeType : byte { None = 0x00, @@ -3317,7 +3299,7 @@ namespace Avalonia.Controls internal void UpdateRenderBounds(Size arrangeSize) { _lastArrangeSize = arrangeSize; - this.InvalidateVisual(); + InvalidateVisual(); } private static Size _lastArrangeSize; diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 7408bff902..2cf0fc3ec4 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -14,8 +14,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty SourceProperty = - AvaloniaProperty.Register(nameof(Source)); + public static readonly StyledProperty SourceProperty = + AvaloniaProperty.Register(nameof(Source)); /// /// Defines the property. @@ -42,7 +42,7 @@ namespace Avalonia.Controls /// Gets or sets the image that will be displayed. /// [Content] - public IImage Source + public IImage? Source { get { return GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } @@ -66,6 +66,7 @@ namespace Avalonia.Controls set { SetValue(StretchDirectionProperty, value); } } + /// protected override bool BypassFlowDirectionPolicies => true; /// diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 59b5bf48a5..42674cb481 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.Diagnostics.CodeAnalysis; using Avalonia.Automation.Peers; using Avalonia.Collections; using Avalonia.Controls.Generators; @@ -17,7 +16,6 @@ using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.Styling; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -106,7 +104,6 @@ namespace Avalonia.Controls private Tuple? _containerBeingPrepared; private ScrollViewer? _scrollViewer; private ItemsPresenter? _itemsPresenter; - private IScrollSnapPointsInfo? _scrolSnapPointInfo; /// /// Initializes a new instance of the class. @@ -221,6 +218,7 @@ namespace Avalonia.Controls } + /// public event EventHandler HorizontalSnapPointsChanged { add @@ -240,6 +238,7 @@ namespace Avalonia.Controls } } + /// public event EventHandler VerticalSnapPointsChanged { add @@ -424,13 +423,12 @@ namespace Avalonia.Controls /// true if the item is (or is eligible to be) its own container; otherwise, false. protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true; + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); _scrollViewer = e.NameScope.Find("PART_ScrollViewer"); _itemsPresenter = e.NameScope.Find("PART_ItemsPresenter"); - - _scrolSnapPointInfo = _itemsPresenter as IScrollSnapPointsInfo; } /// @@ -477,11 +475,13 @@ namespace Avalonia.Controls base.OnKeyDown(e); } + /// protected override AutomationPeer OnCreateAutomationPeer() { return new ItemsControlAutomationPeer(this); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -748,11 +748,13 @@ namespace Avalonia.Controls return true; } + /// public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment) { return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List(); } + /// public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset) { offset = 0; diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index ce254684b7..387dc27562 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls .AddClassHandler((x, e) => x.OnLayoutTransformChanged(e)); ChildProperty.Changed - .AddClassHandler((x, e) => x.OnChildChanged(e)); + .AddClassHandler((x, _) => x.OnChildChanged()); UseRenderTransformProperty.Changed .AddClassHandler((x, e) => x.OnUseRenderTransformPropertyChanged(e)); @@ -146,7 +146,7 @@ namespace Avalonia.Controls return transformedDesiredSize; } - IDisposable? _renderTransformChangedEvent; + private IDisposable? _renderTransformChangedEvent; private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e) { @@ -167,8 +167,7 @@ namespace Avalonia.Controls .Subscribe( (x) => { - var target2 = x.Sender as LayoutTransformControl; - if (target2 != null) + if (x.Sender is LayoutTransformControl target2) { target2.LayoutTransform = target2.RenderTransform; } @@ -182,7 +181,7 @@ namespace Avalonia.Controls } } - private void OnChildChanged(AvaloniaPropertyChangedEventArgs e) + private void OnChildChanged() { if (null != TransformRoot) { @@ -206,18 +205,18 @@ namespace Avalonia.Controls /// /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). /// - private Size _childActualSize = default; + private Size _childActualSize; /// /// RenderTransform/MatrixTransform applied to TransformRoot. /// - private MatrixTransform _matrixTransform = new MatrixTransform(); + private readonly MatrixTransform _matrixTransform = new(); /// /// Transformation matrix corresponding to _matrixTransform. /// private Matrix _transformation; - private IDisposable? _transformChangedEvent = null; + private IDisposable? _transformChangedEvent; /// /// Returns true if Size a is smaller than Size b in either dimension. @@ -263,10 +262,7 @@ namespace Avalonia.Controls // Get the transform matrix and apply it _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); - if (null != _matrixTransform) - { - _matrixTransform.Matrix = _transformation; - } + _matrixTransform.Matrix = _transformation; // New transform means re-layout is necessary InvalidateMeasure(); diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index 080326606e..5a3eb47ce4 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -178,12 +178,11 @@ namespace Avalonia.Controls } } - - } Type IStyleable.StyleKey => typeof(TextBox); + /// protected override void OnGotFocus(GotFocusEventArgs e) { if (HidePromptOnLeave == true && MaskProvider != null) @@ -193,6 +192,7 @@ namespace Avalonia.Controls base.OnGotFocus(e); } + /// protected override async void OnKeyDown(KeyEventArgs e) { if (MaskProvider == null) @@ -271,15 +271,17 @@ namespace Avalonia.Controls } } + /// protected override void OnLostFocus(RoutedEventArgs e) { - if (HidePromptOnLeave == true && MaskProvider != null) + if (HidePromptOnLeave && MaskProvider != null) { Text = MaskProvider.ToString(!HidePromptOnLeave, true); } base.OnLostFocus(e); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { void UpdateMaskProvider() @@ -357,6 +359,8 @@ namespace Avalonia.Controls } base.OnPropertyChanged(change); } + + /// protected override void OnTextInput(TextInputEventArgs e) { _ignoreTextChanges = true; @@ -423,7 +427,7 @@ namespace Avalonia.Controls return startPosition; } - private void RefreshText(MaskedTextProvider provider, int position) + private void RefreshText(MaskedTextProvider? provider, int position) { if (provider != null) { diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 6b9e378d3d..a94a1ee983 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -16,19 +16,17 @@ namespace Avalonia.Controls private IPlatformHandle? _nativeControlHandle; private bool _queuedForDestruction; private bool _queuedForMoveResize; - private readonly List _propertyChangedSubscriptions = new List(); + private readonly List _propertyChangedSubscriptions = new(); + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; var visual = (Visual)this; while (visual != null) { - if (visual is Visual v) - { - v.PropertyChanged += PropertyChangedHandler; - _propertyChangedSubscriptions.Add(v); - } + visual.PropertyChanged += PropertyChangedHandler; + _propertyChangedSubscriptions.Add(visual); visual = visual.GetVisualParent(); } @@ -42,15 +40,13 @@ namespace Avalonia.Controls EnqueueForMoveResize(); } + /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = null; - if (_propertyChangedSubscriptions != null) - { - foreach (var v in _propertyChangedSubscriptions) - v.PropertyChanged -= PropertyChangedHandler; - _propertyChangedSubscriptions.Clear(); - } + foreach (var v in _propertyChangedSubscriptions) + v.PropertyChanged -= PropertyChangedHandler; + _propertyChangedSubscriptions.Clear(); UpdateHost(); } @@ -128,7 +124,7 @@ namespace Avalonia.Controls return new Rect(position.Value, bounds.Size); } - void EnqueueForMoveResize() + private void EnqueueForMoveResize() { if(_queuedForMoveResize) return; diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 9c1fb93a48..ab64416a2c 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -12,10 +12,10 @@ namespace Avalonia.Controls public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty); - private static readonly AttachedProperty s_nativeMenuInfoProperty = - AvaloniaProperty.RegisterAttached("___NativeMenuInfo"); - - class NativeMenuInfo + private static readonly AttachedProperty s_nativeMenuInfoProperty = + AvaloniaProperty.RegisterAttached("___NativeMenuInfo"); + + private sealed class NativeMenuInfo { public bool ChangingIsExported { get; set; } public ITopLevelNativeMenuExporter? Exporter { get; } @@ -33,7 +33,7 @@ namespace Avalonia.Controls } } - static NativeMenuInfo GetInfo(TopLevel target) + private static NativeMenuInfo GetInfo(TopLevel target) { var rv = target.GetValue(s_nativeMenuInfoProperty); if (rv == null) @@ -45,18 +45,18 @@ namespace Avalonia.Controls return rv; } - static void SetIsNativeMenuExported(TopLevel tl, bool value) + private static void SetIsNativeMenuExported(TopLevel tl, bool value) { GetInfo(tl).ChangingIsExported = true; tl.SetValue(IsNativeMenuExportedProperty, value); } - public static readonly AttachedProperty MenuProperty - = AvaloniaProperty.RegisterAttached("Menu"); + public static readonly AttachedProperty MenuProperty + = AvaloniaProperty.RegisterAttached("Menu"); - public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu); + public static void SetMenu(AvaloniaObject o, NativeMenu? menu) => o.SetValue(MenuProperty, menu); - public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); + public static NativeMenu? GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); static NativeMenu() { diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 3464857131..79719912ea 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -34,8 +34,8 @@ namespace Avalonia.Controls.Primitives public static readonly AttachedProperty AdornerProperty = AvaloniaProperty.RegisterAttached("Adorner"); - private static readonly AttachedProperty s_adornedElementInfoProperty = - AvaloniaProperty.RegisterAttached("AdornedElementInfo"); + private static readonly AttachedProperty s_adornedElementInfoProperty = + AvaloniaProperty.RegisterAttached("AdornedElementInfo"); private static readonly AttachedProperty s_savedAdornerLayerProperty = AvaloniaProperty.RegisterAttached("SavedAdornerLayer"); @@ -159,8 +159,8 @@ namespace Avalonia.Controls.Primitives return; } - AdornerLayer.SetAdornedElement(adorner, visual); - AdornerLayer.SetIsClipEnabled(adorner, false); + SetAdornedElement(adorner, visual); + SetIsClipEnabled(adorner, false); ((ISetLogicalParent) adorner).SetParent(visual); layer.Children.Add(adorner); @@ -177,6 +177,7 @@ namespace Avalonia.Controls.Primitives ((ISetLogicalParent) adorner).SetParent(null); } + /// protected override Size MeasureOverride(Size availableSize) { foreach (var child in Children) @@ -199,6 +200,7 @@ namespace Avalonia.Controls.Primitives return default; } + /// protected override Size ArrangeOverride(Size finalSize) { foreach (var child in Children) @@ -217,7 +219,7 @@ namespace Avalonia.Controls.Primitives } else { - ArrangeChild((Control) child, finalSize); + ArrangeChild(child, finalSize); } } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 3b68cd2ae8..d6cd71aedc 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -120,7 +120,7 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); - private bool _isOpenRequested = false; + private bool _isOpenRequested; private bool _isOpen; private bool _ignoreIsOpenChanged; private PopupOpenState? _openState; @@ -377,9 +377,9 @@ namespace Avalonia.Controls.Primitives popupHost.SetChild(Child); ((ISetLogicalParent)popupHost).SetParent(this); - if (InheritsTransform && placementTarget is Control c) + if (InheritsTransform) { - TransformTrackingHelper.Track(c, PlacementTargetTransformChanged) + TransformTrackingHelper.Track(placementTarget, PlacementTargetTransformChanged) .DisposeWith(handlerCleanup); } else @@ -518,6 +518,7 @@ namespace Avalonia.Controls.Primitives Close(); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -579,7 +580,7 @@ namespace Avalonia.Controls.Primitives var scaleX = 1.0; var scaleY = 1.0; - if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m) + if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is { } m) { scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); @@ -623,6 +624,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override AutomationPeer OnCreateAutomationPeer() { return new PopupAutomationPeer(this); @@ -850,7 +852,7 @@ namespace Avalonia.Controls.Primitives var popupHost = _openState.PopupHost; - return popupHost != null && ((Visual)popupHost).IsVisualAncestorOf(visual); + return ((Visual)popupHost).IsVisualAncestorOf(visual); } public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 5210362505..8d79d62dc4 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -3,16 +3,14 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Xml.Linq; -using Avalonia.Controls.Generators; using Avalonia.Controls.Selection; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { @@ -255,6 +253,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the model that holds the current selection. /// + [AllowNull] protected ISelectionModel Selection { get @@ -399,6 +398,7 @@ namespace Avalonia.Controls.Primitives return null; } + /// protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { base.ItemsCollectionChanged(sender!, e); @@ -409,12 +409,14 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); AutoScrollToSelectedItemIfNecessary(); } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -431,6 +433,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index) { base.PrepareContainerForItemOverride(element, item, index); @@ -447,12 +450,14 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex) { base.ContainerIndexChangedOverride(container, oldIndex, newIndex); MarkContainerSelected(container, Selection.IsSelected(newIndex)); } + /// protected internal override void ClearContainerForItemOverride(Control element) { base.ClearContainerForItemOverride(element); @@ -463,7 +468,7 @@ namespace Avalonia.Controls.Primitives KeyboardNavigation.SetTabOnceActiveElement(panel, null); } - if (element is ISelectable selectable) + if (element is ISelectable) MarkContainerSelected(element, false); } @@ -498,7 +503,8 @@ namespace Avalonia.Controls.Primitives DataValidationErrors.SetError(this, error); } } - + + /// protected override void OnInitialized() { base.OnInitialized(); @@ -509,6 +515,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnTextInput(TextInputEventArgs e) { if (!e.Handled) @@ -551,6 +558,7 @@ namespace Avalonia.Controls.Primitives base.OnTextInput(e); } + /// protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); @@ -582,6 +590,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -592,7 +601,7 @@ namespace Avalonia.Controls.Primitives } if (change.Property == ItemsProperty && _updateState is null && _selection is object) { - var newValue = change.GetNewValue(); + var newValue = change.GetNewValue(); _selection.Source = newValue; if (newValue is null) @@ -695,7 +704,7 @@ namespace Avalonia.Controls.Primitives { if (multi) { - if (Selection.IsSelected(index) == true) + if (Selection.IsSelected(index)) { Selection.Deselect(index); } @@ -716,12 +725,10 @@ namespace Avalonia.Controls.Primitives Selection.Select(index); } - if (Presenter?.Panel != null) + if (Presenter?.Panel is { } panel) { var container = ContainerFromIndex(index); - KeyboardNavigation.SetTabOnceActiveElement( - (InputElement)Presenter.Panel, - container); + KeyboardNavigation.SetTabOnceActiveElement(panel, container); } } @@ -940,7 +947,7 @@ namespace Avalonia.Controls.Primitives private void UpdateContainerSelection() { - if (Presenter?.Panel is Panel panel) + if (Presenter?.Panel is { } panel) { foreach (var container in panel.Children) { diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 9a684c4534..d8874832bd 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -290,12 +290,6 @@ namespace Avalonia.Controls.Primitives ApplyTemplatedParent(child, this); ((ISetLogicalParent)child).SetParent(this); VisualChildren.Add(child); - - // Existing code kinda expect to see a NameScope even if it's empty - if (nameScope == null) - { - nameScope = new NameScope(); - } var e = new TemplateAppliedEventArgs(nameScope); OnApplyTemplate(e); @@ -320,6 +314,7 @@ namespace Avalonia.Controls.Primitives return this; } + /// protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { var count = VisualChildren.Count; diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs index 949532cb16..962fba361e 100644 --- a/src/Avalonia.Controls/Primitives/TextSearch.cs +++ b/src/Avalonia.Controls/Primitives/TextSearch.cs @@ -11,15 +11,15 @@ namespace Avalonia.Controls.Primitives /// Defines the Text attached property. /// This text will be considered during text search in (such as ) /// - public static readonly AttachedProperty TextProperty - = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch)); + public static readonly AttachedProperty TextProperty + = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch)); /// /// Sets the for a control. /// /// The control /// The search text to set - public static void SetText(Control control, string text) + public static void SetText(Control control, string? text) { control.SetValue(TextProperty, text); } @@ -29,7 +29,7 @@ namespace Avalonia.Controls.Primitives /// /// The control /// The property value - public static string GetText(Control control) + public static string? GetText(Control control) { return control.GetValue(TextProperty); } diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index 14ec7a2849..9e8d1478fa 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -5,7 +5,6 @@ using System; using Avalonia.Controls.Metadata; -using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Metadata; @@ -31,14 +30,14 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty OrientationProperty = ScrollBar.OrientationProperty.AddOwner(); - public static readonly StyledProperty ThumbProperty = - AvaloniaProperty.Register(nameof(Thumb)); + public static readonly StyledProperty ThumbProperty = + AvaloniaProperty.Register(nameof(Thumb)); - public static readonly StyledProperty