From 3304d646c007d3cbe72784bde5227ed76133d377 Mon Sep 17 00:00:00 2001 From: Fusion86 Date: Sun, 15 Nov 2020 18:49:23 +0100 Subject: [PATCH 01/56] Fix crash when KeyBindings change while they are being handled --- src/Avalonia.Input/KeyboardDevice.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 6f4cb7a35c..9a42e3d3a5 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -211,12 +211,18 @@ namespace Avalonia.Input { var bindings = (currentHandler as IInputElement)?.KeyBindings; if (bindings != null) - foreach (var binding in bindings) + { + // Create a copy of the KeyBindings list. + // If we don't do this the foreach loop will throw an InvalidOperationException when the KeyBindings list is changed. + // This can happen when a new view is loaded which adds its own KeyBindings to the handler. + var cpy = bindings.ToArray(); + foreach (var binding in cpy) { if (ev.Handled) break; binding.TryHandle(ev); } + } currentHandler = currentHandler.VisualParent; } From 38ce4a2a2891ef409c90f73790de05c0bc1ef314 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Tue, 8 Dec 2020 18:41:40 +0300 Subject: [PATCH 02/56] Added ability to use non well known property types in style selector --- .../Activators/PropertyEqualsActivator.cs | 2 +- .../Styling/PropertyEqualsSelector.cs | 32 +++++++++++++++++-- .../SelectorTests_PropertyEquals.cs | 20 ++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs b/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs index 9e30e4fa14..8d446ebc9c 100644 --- a/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs +++ b/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs @@ -33,6 +33,6 @@ namespace Avalonia.Styling.Activators void IObserver.OnCompleted() { } void IObserver.OnError(Exception error) { } - void IObserver.OnNext(object value) => PublishNext(Equals(value, _value)); + void IObserver.OnNext(object value) => PublishNext(PropertyEqualsSelector.Compare(_property.PropertyType, value, _value)); } } diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index cdd985ac80..5d9c3fe56b 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -1,4 +1,6 @@ using System; +using System.ComponentModel; +using System.Globalization; using System.Text; using Avalonia.Styling.Activators; @@ -75,11 +77,37 @@ namespace Avalonia.Styling } else { - var result = (control.GetValue(_property) ?? string.Empty).Equals(_value); - return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; + return Compare(_property.PropertyType, control.GetValue(_property), _value) + ? SelectorMatch.AlwaysThisInstance + : SelectorMatch.NeverThisInstance; } + } protected override Selector? MovePrevious() => _previous; + + internal static bool Compare(Type propertyType, object propertyValue, object? value) + { + if (propertyType == typeof(object) && + propertyValue?.GetType() is Type inferredType) + { + propertyType = inferredType; + } + + var valueType = value?.GetType(); + + if (valueType is null || propertyType.IsAssignableFrom(valueType)) + { + return Equals(propertyValue, value); + } + + var converter = TypeDescriptor.GetConverter(propertyType); + if (converter?.CanConvertFrom(valueType) == true) + { + return Equals(propertyValue, converter.ConvertFrom(null, CultureInfo.InvariantCulture, value)); + } + + return false; + } } } diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs index e149410152..581a655c8e 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs @@ -22,6 +22,20 @@ namespace Avalonia.Styling.UnitTests Assert.False(await activator.Take(1)); } + [Fact] + public async Task PropertyEquals_Matches_When_Property_Has_Matching_Value_And_Different_Type() + { + var control = new TextBlock(); + var target = default(Selector).PropertyEquals(TextBlock.TagProperty, "Bar"); + var activator = target.Match(control).Activator.ToObservable(); + + Assert.False(await activator.Take(1)); + control.Tag = FooBar.Bar; + Assert.True(await activator.Take(1)); + control.Tag = null; + Assert.False(await activator.Take(1)); + } + [Fact] public void OfType_PropertyEquals_Doesnt_Match_Control_Of_Wrong_Type() { @@ -40,5 +54,11 @@ namespace Avalonia.Styling.UnitTests Assert.Equal("TextBlock[Text=foo]", target.ToString()); } + + private enum FooBar + { + Foo, + Bar + } } } From f75f6736260b27feb997d28b44a4e3605f5204e8 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Fri, 11 Dec 2020 16:50:12 +0300 Subject: [PATCH 03/56] Added more test cases --- .../SelectorTests_PropertyEquals.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs index 581a655c8e..7689a458ae 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs @@ -22,15 +22,18 @@ namespace Avalonia.Styling.UnitTests Assert.False(await activator.Take(1)); } - [Fact] - public async Task PropertyEquals_Matches_When_Property_Has_Matching_Value_And_Different_Type() + [Theory] + [InlineData("Bar", FooBar.Bar)] + [InlineData("352", 352)] + [InlineData("0.1", 0.1)] + public async Task PropertyEquals_Matches_When_Property_Has_Matching_Value_And_Different_Type(string literal, object value) { var control = new TextBlock(); - var target = default(Selector).PropertyEquals(TextBlock.TagProperty, "Bar"); + var target = default(Selector).PropertyEquals(TextBlock.TagProperty, literal); var activator = target.Match(control).Activator.ToObservable(); Assert.False(await activator.Take(1)); - control.Tag = FooBar.Bar; + control.Tag = value; Assert.True(await activator.Take(1)); control.Tag = null; Assert.False(await activator.Take(1)); From ae4e0741d6922f73b3eb89a16c878e5aea24ca1b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 23:07:16 -0400 Subject: [PATCH 04/56] Add MemberNotNullAttribute and MemberNotNullWhenAttribute attributes --- .../Metadata/NullableAttributes.cs | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Metadata/NullableAttributes.cs b/src/Avalonia.Base/Metadata/NullableAttributes.cs index 91f5e81863..b6f0f3a47c 100644 --- a/src/Avalonia.Base/Metadata/NullableAttributes.cs +++ b/src/Avalonia.Base/Metadata/NullableAttributes.cs @@ -1,6 +1,5 @@ #pragma warning disable MA0048 // File name must match type name #define INTERNAL_NULLABLE_ATTRIBUTES -#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 // https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs @@ -10,6 +9,7 @@ namespace System.Diagnostics.CodeAnalysis { +#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 /// Specifies that null is allowed as an input even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] #if INTERNAL_NULLABLE_ATTRIBUTES @@ -136,5 +136,82 @@ namespace System.Diagnostics.CodeAnalysis /// Gets the condition parameter value. public bool ParameterValue { get; } } -} +#endif // NETSTANDARD2_0 attributes + +#if NETSTANDARD2_1 || NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 + /// + /// Specifies that the method or property will ensure that the listed field and property members have + /// not- values. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public #endif + sealed class MemberNotNullAttribute : Attribute + { + /// Gets field or property member names. + public string[] Members { get; } + + /// Initializes the attribute with a field or property member. + /// The field or property member that is promised to be not-null. + public MemberNotNullAttribute(string member) + { + Members = new[] { member }; + } + + /// Initializes the attribute with the list of field and property members. + /// The list of field and property members that are promised to be not-null. + public MemberNotNullAttribute(params string[] members) + { + Members = members; + } + } + + /// + /// Specifies that the method or property will ensure that the listed field and property members have + /// non- values when returning with the specified return value condition. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class MemberNotNullWhenAttribute : Attribute + { + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, + /// the associated parameter will not be . + /// + /// The field or property member that is promised to be not-. + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// + /// The return value condition. If the method returns this value, + /// the associated parameter will not be . + /// + /// The list of field and property members that are promised to be not-null. + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + } +#endif // NETSTANDARD2_1 attributes +} + From 522f6b97ae755be721538a6b0727c7388a481434 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 23:07:40 -0400 Subject: [PATCH 05/56] Enable nullable on Avalonia.Diagnostics --- .../Avalonia.Diagnostics.csproj | 4 ++ .../Diagnostics/Controls/ThicknessEditor.cs | 8 ++-- .../Converters/BoolToOpacityConverter.cs | 11 +++-- .../Converters/EnumToCheckedConverter.cs | 4 +- .../Diagnostics/DevTools.cs | 4 +- .../Diagnostics/Models/ConsoleContext.cs | 4 +- .../Diagnostics/Models/EventChainLink.cs | 4 +- .../ViewModels/AvaloniaPropertyViewModel.cs | 42 +++++++------------ .../ViewModels/ClrPropertyViewModel.cs | 7 ++-- .../ViewModels/ConsoleViewModel.cs | 3 +- .../ViewModels/ControlDetailsViewModel.cs | 16 +++---- .../ViewModels/ControlLayoutViewModel.cs | 10 ++--- .../ViewModels/EventOwnerTreeNode.cs | 2 +- .../Diagnostics/ViewModels/EventTreeNode.cs | 19 ++++----- .../ViewModels/EventTreeNodeBase.cs | 6 +-- .../ViewModels/EventsPageViewModel.cs | 11 +++-- .../Diagnostics/ViewModels/FilterViewModel.cs | 17 ++++---- .../Diagnostics/ViewModels/FiredEvent.cs | 15 +++---- .../Diagnostics/ViewModels/LogicalTreeNode.cs | 8 ++-- .../Diagnostics/ViewModels/MainViewModel.cs | 19 +++++---- .../ViewModels/PropertyViewModel.cs | 2 +- .../ViewModels/ResourceSetterViewModel.cs | 2 +- .../Diagnostics/ViewModels/SetterViewModel.cs | 4 +- .../Diagnostics/ViewModels/TreeNode.cs | 16 +++---- .../ViewModels/TreeNodeCollection.cs | 29 ++++++++++--- .../ViewModels/TreePageViewModel.cs | 14 +++---- .../Diagnostics/ViewModels/ViewModelBase.cs | 10 ++--- .../Diagnostics/ViewModels/VisualTreeNode.cs | 8 ++-- .../Diagnostics/Views/ConsoleView.xaml.cs | 8 +++- .../Diagnostics/Views/MainView.xaml.cs | 6 ++- .../Diagnostics/Views/MainWindow.xaml.cs | 31 +++++++++----- .../Diagnostics/Views/TreePageView.xaml.cs | 14 ++++--- .../Diagnostics/VisualTreeDebug.cs | 2 +- 33 files changed, 198 insertions(+), 162 deletions(-) diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index db8684747d..35de491668 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -3,12 +3,16 @@ netstandard2.0 Avalonia Avalonia.Diagnostics + enable %(Filename) + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs index e5b3b080e2..cb98fb70f3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs @@ -10,8 +10,8 @@ namespace Avalonia.Diagnostics.Controls AvaloniaProperty.RegisterDirect(nameof(Thickness), o => o.Thickness, (o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay); - public static readonly DirectProperty HeaderProperty = - AvaloniaProperty.RegisterDirect(nameof(Header), o => o.Header, + public static readonly DirectProperty HeaderProperty = + AvaloniaProperty.RegisterDirect(nameof(Header), o => o.Header, (o, v) => o.Header = v); public static readonly DirectProperty IsPresentProperty = @@ -36,7 +36,7 @@ namespace Avalonia.Diagnostics.Controls AvaloniaProperty.Register(nameof(Highlight)); private Thickness _thickness; - private string _header; + private string? _header; private bool _isPresent = true; private double _left; private double _top; @@ -50,7 +50,7 @@ namespace Avalonia.Diagnostics.Controls set => SetAndRaise(ThicknessProperty, ref _thickness, value); } - public string Header + public string? Header { get => _header; set => SetAndRaise(HeaderProperty, ref _header, value); diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs index 63ac3ab62f..0b9044e65e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs @@ -8,12 +8,17 @@ namespace Avalonia.Diagnostics.Converters { public double Opacity { get; set; } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (bool)value ? 1d : Opacity; + if (value is bool boolean && boolean) + { + return 1d; + } + + return Opacity; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs index 8d10981ba7..4863782f44 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs @@ -7,12 +7,12 @@ namespace Avalonia.Diagnostics.Converters { internal class EnumToCheckedConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return Equals(value, parameter); } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is bool isChecked && isChecked) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 7942d22962..78e808595f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -6,8 +6,6 @@ using Avalonia.Diagnostics.Views; using Avalonia.Input; using Avalonia.Interactivity; -#nullable enable - namespace Avalonia.Diagnostics { public static class DevTools @@ -74,7 +72,7 @@ namespace Avalonia.Diagnostics private static void DevToolsClosed(object sender, EventArgs e) { var window = (MainWindow)sender; - s_open.Remove(window.Root); + s_open.Remove(window.Root!); window.Closed -= DevToolsClosed; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs b/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs index 5927bd785e..4f4579c7d9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs @@ -22,8 +22,8 @@ The following commands are available: clear(): Clear the output history "; - public dynamic e { get; internal set; } - public dynamic root { get; internal set; } + public dynamic? e { get; internal set; } + public dynamic? root { get; internal set; } internal static object NoOutput { get; } = new object(); diff --git a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs index 36fe12d89c..4f493bdcc2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs @@ -7,9 +7,7 @@ namespace Avalonia.Diagnostics.Models { public EventChainLink(object handler, bool handled, RoutingStrategies route) { - Contract.Requires(handler != null); - - Handler = handler; + Handler = handler ?? throw new ArgumentNullException(nameof(handler)); Handled = handled; Route = route; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index a9353eba8b..21265f2cfc 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -1,5 +1,4 @@ -using System.ComponentModel; -using Avalonia.Collections; +using System.Diagnostics.CodeAnalysis; namespace Avalonia.Diagnostics.ViewModels { @@ -7,7 +6,7 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly AvaloniaObject _target; private string _type; - private object _value; + private object? _value; private string _priority; private string _group; @@ -20,12 +19,6 @@ namespace Avalonia.Diagnostics.ViewModels $"[{property.OwnerType.Name}.{property.Name}]" : property.Name; - if (property.IsDirect) - { - _group = "Properties"; - Priority = "Direct"; - } - Update(); } @@ -34,11 +27,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Name { get; } public bool IsAttached => Property.IsAttached; - public string Priority - { - get => _priority; - private set => RaiseAndSetIfChanged(ref _priority, value); - } + public string Priority => _priority; public override string Type => _type; @@ -56,40 +45,37 @@ namespace Avalonia.Diagnostics.ViewModels } } - public override string Group - { - get => _group; - } + public override string Group => _group; + [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))] public override void Update() { if (Property.IsDirect) { RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); + RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority)); + + _group = "Properties"; } else { var val = _target.GetDiagnostic(Property); RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); + RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); if (val != null) { - SetGroup(IsAttached ? "Attached Properties" : "Properties"); - Priority = val.Priority.ToString(); + RaiseAndSetIfChanged(ref _priority, val.Priority.ToString(), nameof(Priority)); + RaiseAndSetIfChanged(ref _group, IsAttached ? "Attached Properties" : "Properties", nameof(Group)); } else { - SetGroup(Priority = "Unset"); + RaiseAndSetIfChanged(ref _priority, "Unset", nameof(Priority)); + RaiseAndSetIfChanged(ref _group, "Unset", nameof(Group)); } } } - - private void SetGroup(string group) - { - RaiseAndSetIfChanged(ref _group, group, nameof(Group)); - } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index af5e254204..19e4a702eb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace Avalonia.Diagnostics.ViewModels @@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly object _target; private string _type; - private object _value; + private object? _value; public ClrPropertyViewModel(object o, PropertyInfo property) { @@ -47,11 +47,12 @@ namespace Avalonia.Diagnostics.ViewModels } } + [MemberNotNull(nameof(_type))] public override void Update() { var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); + RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs index 0e0c44ded8..717b49d074 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs @@ -15,11 +15,12 @@ namespace Avalonia.Diagnostics.ViewModels private int _historyIndex = -1; private string _input; private bool _isVisible; - private ScriptState _state; + private ScriptState? _state; public ConsoleViewModel(Action updateContext) { _context = new ConsoleContext(this); + _input = string.Empty; _updateContext = updateContext; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index b1ff8ae98d..74709cb91a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -18,10 +18,10 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly IVisual _control; private readonly IDictionary> _propertyIndex; - private AvaloniaPropertyViewModel _selectedProperty; + private AvaloniaPropertyViewModel? _selectedProperty; private bool _snapshotStyles; private bool _showInactiveStyles; - private string _styleStatus; + private string? _styleStatus; public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) { @@ -83,7 +83,8 @@ namespace Avalonia.Diagnostics.ViewModels { foreach (var setter in style.Setters) { - if (setter is Setter regularSetter) + if (setter is Setter regularSetter + && regularSetter.Property != null) { var setterValue = regularSetter.Value; @@ -115,13 +116,14 @@ namespace Avalonia.Diagnostics.ViewModels } } - private (object resourceKey, bool isDynamic)? GetResourceInfo(object value) + private (object resourceKey, bool isDynamic)? GetResourceInfo(object? value) { if (value is StaticResourceExtension staticResource) { return (staticResource.ResourceKey, false); } - else if (value is DynamicResourceExtension dynamicResource) + else if (value is DynamicResourceExtension dynamicResource + && dynamicResource.ResourceKey != null) { return (dynamicResource.ResourceKey, true); } @@ -137,7 +139,7 @@ namespace Avalonia.Diagnostics.ViewModels public ObservableCollection PseudoClasses { get; } - public AvaloniaPropertyViewModel SelectedProperty + public AvaloniaPropertyViewModel? SelectedProperty { get => _selectedProperty; set => RaiseAndSetIfChanged(ref _selectedProperty, value); @@ -155,7 +157,7 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _showInactiveStyles, value); } - public string StyleStatus + public string? StyleStatus { get => _styleStatus; set => RaiseAndSetIfChanged(ref _styleStatus, value); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs index b0718bc6ce..f691698488 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -12,14 +12,14 @@ namespace Avalonia.Diagnostics.ViewModels private readonly IVisual _control; private Thickness _borderThickness; private double _height; - private string _heightConstraint; + private string? _heightConstraint; private HorizontalAlignment _horizontalAlignment; private Thickness _marginThickness; private Thickness _paddingThickness; private bool _updatingFromControl; private VerticalAlignment _verticalAlignment; private double _width; - private string _widthConstraint; + private string? _widthConstraint; public ControlLayoutViewModel(IVisual control) { @@ -80,13 +80,13 @@ namespace Avalonia.Diagnostics.ViewModels private set => RaiseAndSetIfChanged(ref _height, value); } - public string WidthConstraint + public string? WidthConstraint { get => _widthConstraint; private set => RaiseAndSetIfChanged(ref _widthConstraint, value); } - public string HeightConstraint + public string? HeightConstraint { get => _heightConstraint; private set => RaiseAndSetIfChanged(ref _heightConstraint, value); @@ -112,7 +112,7 @@ namespace Avalonia.Diagnostics.ViewModels { if (_control is IAvaloniaObject ao) { - string CreateConstraintInfo(StyledProperty minProperty, StyledProperty maxProperty) + string? CreateConstraintInfo(StyledProperty minProperty, StyledProperty maxProperty) { bool hasMin = ao.IsSet(minProperty); bool hasMax = ao.IsSet(maxProperty); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs index b56374d353..5b7ddc98ee 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs @@ -27,7 +27,7 @@ namespace Avalonia.Diagnostics.ViewModels if (_updateChildren && value != null) { - foreach (var child in Children) + foreach (var child in Children!) { try { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index ea54302ebd..f6f57b769d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -11,16 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly EventsPageViewModel _parentViewModel; private bool _isRegistered; - private FiredEvent _currentEvent; + private FiredEvent? _currentEvent; public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsPageViewModel vm) : base(parent, @event.Name) { - Contract.Requires(@event != null); - Contract.Requires(vm != null); - - Event = @event; - _parentViewModel = vm; + Event = @event ?? throw new ArgumentNullException(nameof(@event)); + _parentViewModel = vm ?? throw new ArgumentNullException(nameof(vm)); } public RoutedEvent Event { get; } @@ -73,7 +70,7 @@ namespace Avalonia.Diagnostics.ViewModels var handled = e.Handled; var route = e.Route; - Action handler = delegate + void handler() { if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) { @@ -98,14 +95,16 @@ namespace Avalonia.Diagnostics.ViewModels private static bool BelongsToDevTool(IVisual v) { - while (v != null) + var current = v; + + while (current != null) { - if (v is MainView || v is MainWindow) + if (current is MainView || current is MainWindow) { return true; } - v = v.VisualParent; + current = current.VisualParent; } return false; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs index c27cad29e8..e6d7335297 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs @@ -10,14 +10,14 @@ namespace Avalonia.Diagnostics.ViewModels private bool? _isEnabled = false; private bool _isVisible; - protected EventTreeNodeBase(EventTreeNodeBase parent, string text) + protected EventTreeNodeBase(EventTreeNodeBase? parent, string text) { Parent = parent; Text = text; IsVisible = true; } - public IAvaloniaReadOnlyList Children + public IAvaloniaReadOnlyList? Children { get; protected set; @@ -41,7 +41,7 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _isVisible, value); } - public EventTreeNodeBase Parent + public EventTreeNodeBase? Parent { get; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs index 7a157dec62..fbcedb2e74 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; using System.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Models; @@ -23,8 +22,8 @@ namespace Avalonia.Diagnostics.ViewModels }; private readonly MainViewModel _mainViewModel; - private FiredEvent _selectedEvent; - private EventTreeNodeBase _selectedNode; + private FiredEvent? _selectedEvent; + private EventTreeNodeBase? _selectedNode; public EventsPageViewModel(MainViewModel mainViewModel) { @@ -48,13 +47,13 @@ namespace Avalonia.Diagnostics.ViewModels public ObservableCollection RecordedEvents { get; } = new ObservableCollection(); - public FiredEvent SelectedEvent + public FiredEvent? SelectedEvent { get => _selectedEvent; set => RaiseAndSetIfChanged(ref _selectedEvent, value); } - public EventTreeNodeBase SelectedNode + public EventTreeNodeBase? SelectedNode { get => _selectedNode; set => RaiseAndSetIfChanged(ref _selectedNode, value); @@ -99,7 +98,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - static EventTreeNodeBase FindNode(EventTreeNodeBase node, RoutedEvent eventType) + static EventTreeNodeBase? FindNode(EventTreeNodeBase node, RoutedEvent eventType) { if (node is EventTreeNode eventNode && eventNode.Event == eventType) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs index 0d472a5d8f..e592c89892 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs @@ -11,10 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels private readonly Dictionary _errors = new Dictionary(); private string _filterString = string.Empty; private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter; - private string _processedFilter; - private Regex _filterRegex; + private Regex? _filterRegex; - public event EventHandler RefreshFilter; + public event EventHandler? RefreshFilter; + + public bool HasErrors => _errors.Count > 0; + + public event EventHandler? ErrorsChanged; public bool Filter(string input) { @@ -31,13 +34,11 @@ namespace Avalonia.Diagnostics.ViewModels } } - _processedFilter = FilterString.Trim(); - try { var options = RegexOptions.Compiled; var pattern = UseRegexFilter - ? _processedFilter : Regex.Escape(_processedFilter); + ? FilterString.Trim() : Regex.Escape(FilterString.Trim()); if (!UseCaseSensitiveFilter) { options |= RegexOptions.IgnoreCase; @@ -116,9 +117,5 @@ namespace Avalonia.Diagnostics.ViewModels yield return error; } } - - public bool HasErrors => _errors.Count > 0; - - public event EventHandler ErrorsChanged; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs index 5fb528eead..32df2f8745 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs @@ -8,15 +8,12 @@ namespace Avalonia.Diagnostics.ViewModels internal class FiredEvent : ViewModelBase { private readonly RoutedEventArgs _eventArgs; - private EventChainLink _handledBy; + private EventChainLink? _handledBy; public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator) { - Contract.Requires(eventArgs != null); - Contract.Requires(originator != null); - - _eventArgs = eventArgs; - Originator = originator; + _eventArgs = eventArgs ?? throw new ArgumentNullException(nameof(eventArgs)); + Originator = originator ?? throw new ArgumentNullException(nameof(originator)); AddToChain(originator); } @@ -25,7 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels return e == _eventArgs; } - public RoutedEvent Event => _eventArgs.RoutedEvent; + public RoutedEvent Event => _eventArgs.RoutedEvent!; public bool IsHandled => HandledBy?.Handled == true; @@ -38,7 +35,7 @@ namespace Avalonia.Diagnostics.ViewModels if (IsHandled) { return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine + - $"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}"; + $"strategies: {Event.RoutingStrategies}; handled by: {HandledBy!.HandlerName}"; } return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}"; @@ -47,7 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels public EventChainLink Originator { get; } - public EventChainLink HandledBy + public EventChainLink? HandledBy { get => _handledBy; set diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index 38788ef8ee..04215fa8ae 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -7,22 +7,24 @@ namespace Avalonia.Diagnostics.ViewModels { internal class LogicalTreeNode : TreeNode { - public LogicalTreeNode(ILogical logical, TreeNode parent) + public LogicalTreeNode(ILogical logical, TreeNode? parent) : base((Control)logical, parent) { Children = new LogicalTreeNodeCollection(this, logical); } + public override TreeNodeCollection Children { get; } + public static LogicalTreeNode[] Create(object control) { var logical = control as ILogical; - return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null; + return logical != null ? new[] { new LogicalTreeNode(logical, null) } : Array.Empty(); } internal class LogicalTreeNodeCollection : TreeNodeCollection { private readonly ILogical _control; - private IDisposable _subscription; + private IDisposable? _subscription; public LogicalTreeNodeCollection(TreeNode owner, ILogical control) : base(owner) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 49263eafdc..4a945c253f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,10 +1,11 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { @@ -17,8 +18,8 @@ namespace Avalonia.Diagnostics.ViewModels private readonly IDisposable _pointerOverSubscription; private ViewModelBase _content; private int _selectedTab; - private string _focusedControl; - private string _pointerOverElement; + private string? _focusedControl; + private string? _pointerOverElement; private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; @@ -84,6 +85,7 @@ namespace Avalonia.Diagnostics.ViewModels public ViewModelBase Content { get { return _content; } + [MemberNotNull(nameof(_content))] private set { if (_content is TreePageViewModel oldTree && @@ -114,34 +116,35 @@ namespace Avalonia.Diagnostics.ViewModels public int SelectedTab { get { return _selectedTab; } + [MemberNotNull(nameof(_content))] set { _selectedTab = value; switch (value) { - case 0: - Content = _logicalTree; - break; case 1: Content = _visualTree; break; case 2: Content = _events; break; + default: + Content = _logicalTree; + break; } RaisePropertyChanged(); } } - public string FocusedControl + public string? FocusedControl { get { return _focusedControl; } private set { RaiseAndSetIfChanged(ref _focusedControl, value); } } - public string PointerOverElement + public string? PointerOverElement { get { return _pointerOverElement; } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index e23d6f1471..779d74d554 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels public abstract string Value { get; set; } public abstract void Update(); - protected static string ConvertToString(object value) + protected static string ConvertToString(object? value) { if (value is null) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs index a82e13fcfa..2655b0e31f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs @@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics.ViewModels public IBrush Tint { get; } - public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object resourceValue, bool isDynamic) : base(property, resourceValue) + public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue) { Key = resourceKey; Tint = isDynamic ? Brushes.Orange : Brushes.Brown; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs index e835f5a878..1f0e93d751 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs @@ -11,7 +11,7 @@ namespace Avalonia.Diagnostics.ViewModels public string Name { get; } - public object Value { get; } + public object? Value { get; } public bool IsActive { @@ -25,7 +25,7 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _isVisible, value); } - public SetterViewModel(AvaloniaProperty property, object value) + public SetterViewModel(AvaloniaProperty property, object? value) { Property = property; Name = property.Name; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 9363c28705..4cb470eeac 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -9,17 +9,18 @@ using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { - internal class TreeNode : ViewModelBase, IDisposable + internal abstract class TreeNode : ViewModelBase, IDisposable { - private IDisposable _classesSubscription; + private IDisposable? _classesSubscription; private string _classes; private bool _isExpanded; - public TreeNode(IVisual visual, TreeNode parent) + public TreeNode(IVisual visual, TreeNode? parent) { Parent = parent; Type = visual.GetType().Name; Visual = visual; + _classes = string.Empty; if (visual is IControl control) { @@ -51,10 +52,9 @@ namespace Avalonia.Diagnostics.ViewModels } } - public TreeNodeCollection Children + public abstract TreeNodeCollection Children { get; - protected set; } public string Classes @@ -63,7 +63,7 @@ namespace Avalonia.Diagnostics.ViewModels private set { RaiseAndSetIfChanged(ref _classes, value); } } - public string ElementName + public string? ElementName { get; } @@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.ViewModels set { RaiseAndSetIfChanged(ref _isExpanded, value); } } - public TreeNode Parent + public TreeNode? Parent { get; } @@ -92,7 +92,7 @@ namespace Avalonia.Diagnostics.ViewModels public void Dispose() { - _classesSubscription.Dispose(); + _classesSubscription?.Dispose(); Children.Dispose(); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs index 8b4f03bd23..e01258f4cc 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs @@ -3,13 +3,15 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + using Avalonia.Collections; namespace Avalonia.Diagnostics.ViewModels { internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList, IDisposable { - private AvaloniaList _inner; + private AvaloniaList? _inner; public TreeNodeCollection(TreeNode owner) => Owner = owner; @@ -35,14 +37,30 @@ namespace Avalonia.Diagnostics.ViewModels public event NotifyCollectionChangedEventHandler CollectionChanged { - add => _inner.CollectionChanged += value; - remove => _inner.CollectionChanged -= value; + add + { + EnsureInitialized(); + _inner.CollectionChanged += value; + } + remove + { + EnsureInitialized(); + _inner.CollectionChanged -= value; + } } public event PropertyChangedEventHandler PropertyChanged { - add => _inner.PropertyChanged += value; - remove => _inner.PropertyChanged -= value; + add + { + EnsureInitialized(); + _inner.PropertyChanged += value; + } + remove + { + EnsureInitialized(); + _inner.PropertyChanged -= value; + } } public virtual void Dispose() @@ -66,6 +84,7 @@ namespace Avalonia.Diagnostics.ViewModels protected abstract void Initialize(AvaloniaList nodes); + [MemberNotNull(nameof(_inner))] private void EnsureInitialized() { if (_inner is null) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 85a7cb69a3..4b18cf414a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -6,8 +6,8 @@ namespace Avalonia.Diagnostics.ViewModels { internal class TreePageViewModel : ViewModelBase, IDisposable { - private TreeNode _selectedNode; - private ControlDetailsViewModel _details; + private TreeNode? _selectedNode; + private ControlDetailsViewModel? _details; public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes) { @@ -29,7 +29,7 @@ namespace Avalonia.Diagnostics.ViewModels public TreeNode[] Nodes { get; protected set; } - public TreeNode SelectedNode + public TreeNode? SelectedNode { get => _selectedNode; private set @@ -44,7 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - public ControlDetailsViewModel Details + public ControlDetailsViewModel? Details { get => _details; private set @@ -68,7 +68,7 @@ namespace Avalonia.Diagnostics.ViewModels _details?.Dispose(); } - public TreeNode FindNode(IControl control) + public TreeNode? FindNode(IControl control) { foreach (var node in Nodes) { @@ -104,7 +104,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private void ExpandNode(TreeNode node) + private void ExpandNode(TreeNode? node) { if (node != null) { @@ -113,7 +113,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private TreeNode FindNode(TreeNode node, IControl control) + private TreeNode? FindNode(TreeNode node, IControl control) { if (node.Visual == control) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs index 66e9c34657..0bc44d5778 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Avalonia.Diagnostics.ViewModels { internal class ViewModelBase : INotifyPropertyChanged { - private PropertyChangedEventHandler _propertyChanged; + private PropertyChangedEventHandler? _propertyChanged; private List events = new List(); public event PropertyChangedEventHandler PropertyChanged @@ -20,7 +20,7 @@ namespace Avalonia.Diagnostics.ViewModels { } - protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string propertyName = null) + protected bool RaiseAndSetIfChanged([NotNullIfNotNull("value")] ref T field, T value, [CallerMemberName] string propertyName = null!) { if (!EqualityComparer.Default.Equals(field, value)) { @@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels return false; } - protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) + protected void RaisePropertyChanged([CallerMemberName] string propertyName = null!) { var e = new PropertyChangedEventArgs(propertyName); OnPropertyChanged(e); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index bc40edf477..48fa636664 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels { internal class VisualTreeNode : TreeNode { - public VisualTreeNode(IVisual visual, TreeNode parent) + public VisualTreeNode(IVisual visual, TreeNode? parent) : base(visual, parent) { Children = new VisualTreeNodeCollection(this, visual); @@ -20,16 +20,18 @@ namespace Avalonia.Diagnostics.ViewModels public bool IsInTemplate { get; private set; } + public override TreeNodeCollection Children { get; } + public static VisualTreeNode[] Create(object control) { var visual = control as IVisual; - return visual != null ? new[] { new VisualTreeNode(visual, null) } : null; + return visual != null ? new[] { new VisualTreeNode(visual, null) } : Array.Empty(); } internal class VisualTreeNodeCollection : TreeNodeCollection { private readonly IVisual _control; - private IDisposable _subscription; + private IDisposable? _subscription; public VisualTreeNodeCollection(TreeNode owner, IVisual control) : base(owner) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs index ae70b59fde..a018e4180e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs @@ -40,12 +40,16 @@ namespace Avalonia.Diagnostics.Views private void InputKeyDown(object sender, KeyEventArgs e) { - var vm = (ConsoleViewModel)DataContext; + var vm = (ConsoleViewModel?)DataContext; + if (vm is null) + { + return; + } switch (e.Key) { case Key.Enter: - vm.Execute(); + _ = vm.Execute(); e.Handled = true; break; case Key.Up: diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs index 783709e54b..f30973fea9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs @@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views public void ToggleConsole() { - var vm = (MainViewModel)DataContext; + var vm = (MainViewModel?)DataContext; + if (vm is null) + { + return; + } if (_consoleHeight == -1) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 330121321a..fdadf114c9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.Views internal class MainWindow : Window, IStyleHost { private readonly IDisposable _keySubscription; - private TopLevel _root; + private TopLevel? _root; public MainWindow() { @@ -26,7 +26,7 @@ namespace Avalonia.Diagnostics.Views .Subscribe(RawKeyDown); } - public TopLevel Root + public TopLevel? Root { get => _root; set @@ -43,7 +43,7 @@ namespace Avalonia.Diagnostics.Views if (_root != null) { _root.Closed += RootClosed; - DataContext = new MainViewModel(value); + DataContext = new MainViewModel(_root); } else { @@ -53,15 +53,20 @@ namespace Avalonia.Diagnostics.Views } } - IStyleHost IStyleHost.StylingParent => null; + IStyleHost? IStyleHost.StylingParent => null; protected override void OnClosed(EventArgs e) { base.OnClosed(e); _keySubscription.Dispose(); - _root.Closed -= RootClosed; - _root = null; - ((MainViewModel)DataContext)?.Dispose(); + + if (_root != null) + { + _root.Closed -= RootClosed; + _root = null; + } + + ((MainViewModel?)DataContext)?.Dispose(); } private void InitializeComponent() @@ -71,12 +76,20 @@ namespace Avalonia.Diagnostics.Views private void RawKeyDown(RawKeyEventArgs e) { + var vm = (MainViewModel?)DataContext; + if (vm is null) + { + return; + } + const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift; if (e.Modifiers == modifiers) { +#pragma warning disable CS0618 // Type or member is obsolete var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default; - +#pragma warning restore CS0618 // Type or member is obsolete + var control = Root.GetVisualsAt(point, x => { if (x is AdornerLayer || !x.IsVisible) return false; @@ -87,7 +100,6 @@ namespace Avalonia.Diagnostics.Views if (control != null) { - var vm = (MainViewModel)DataContext; vm.SelectControl((IControl)control); } } @@ -97,7 +109,6 @@ namespace Avalonia.Diagnostics.Views { var enable = e.Key == Key.S; - var vm = (MainViewModel)DataContext; vm.EnableSnapshotStyles(enable); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 3e1a238b36..6ad6533e7a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -14,12 +14,13 @@ namespace Avalonia.Diagnostics.Views internal class TreePageView : UserControl { private readonly Panel _adorner; - private AdornerLayer _currentLayer; + private AdornerLayer? _currentLayer; private TreeView _tree; public TreePageView() { InitializeComponent(); + _tree = this.FindControl("tree"); _tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized; _adorner = new Panel @@ -39,7 +40,13 @@ namespace Avalonia.Diagnostics.Views protected void AddAdorner(object sender, PointerEventArgs e) { - var node = (TreeNode)((Control)sender).DataContext; + var node = (TreeNode?)((Control)sender).DataContext; + var vm = (TreePageViewModel?)DataContext; + if (node is null || vm is null) + { + return; + } + var visual = (Visual)node.Visual; _currentLayer = AdornerLayer.GetAdornerLayer(visual); @@ -53,8 +60,6 @@ namespace Avalonia.Diagnostics.Views _currentLayer.Children.Add(_adorner); AdornerLayer.SetAdornedElement(_adorner, visual); - var vm = (TreePageViewModel) DataContext; - if (vm.MainView.ShouldVisualizeMarginPadding) { var paddingBorder = (Border)_adorner.Children[0]; @@ -90,7 +95,6 @@ namespace Avalonia.Diagnostics.Views private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - _tree = this.FindControl("tree"); } private void TreeViewItemMaterialized(object sender, ItemContainerEventArgs e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs index 6f699339e7..4adcd32302 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs @@ -17,7 +17,7 @@ namespace Avalonia.Diagnostics private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent) { - Control control = visual as Control; + Control? control = visual as Control; builder.Append(Indent(indent - 1)); From de0be9326d924ac4675da0880fc2dafd5866bbdf Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 23:30:47 -0400 Subject: [PATCH 06/56] Fix .NET 5 warnings --- .../Diagnostics/DevTools.cs | 6 +++--- .../Diagnostics/ViewLocator.cs | 4 ++-- .../ViewModels/ClrPropertyViewModel.cs | 2 +- .../ViewModels/ControlDetailsViewModel.cs | 19 ++++++++++--------- .../ViewModels/ControlLayoutViewModel.cs | 2 +- .../Diagnostics/ViewModels/EventTreeNode.cs | 4 ++-- .../Diagnostics/ViewModels/FilterViewModel.cs | 5 +++-- .../Diagnostics/ViewModels/MainViewModel.cs | 2 +- .../ViewModels/PropertyViewModel.cs | 6 +++--- .../ViewModels/ResourceSetterViewModel.cs | 6 ++++-- .../Diagnostics/ViewModels/SetterViewModel.cs | 6 ++++-- .../ViewModels/TreeNodeCollection.cs | 4 ++-- .../Diagnostics/ViewModels/ViewModelBase.cs | 2 +- .../Diagnostics/Views/ConsoleView.xaml.cs | 6 +++--- .../Diagnostics/Views/EventsPageView.xaml.cs | 2 +- .../Diagnostics/Views/MainView.xaml.cs | 2 +- .../Diagnostics/Views/MainWindow.xaml.cs | 2 +- .../Diagnostics/Views/TreePageView.xaml.cs | 12 ++++++------ 18 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 78e808595f..0e36c8f9cb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -22,7 +22,7 @@ namespace Avalonia.Diagnostics public static IDisposable Attach(TopLevel root, DevToolsOptions options) { - void PreviewKeyDown(object sender, KeyEventArgs e) + void PreviewKeyDown(object? sender, KeyEventArgs e) { if (options.Gesture.Matches(e)) { @@ -69,9 +69,9 @@ namespace Avalonia.Diagnostics return Disposable.Create(() => window?.Close()); } - private static void DevToolsClosed(object sender, EventArgs e) + private static void DevToolsClosed(object? sender, EventArgs e) { - var window = (MainWindow)sender; + var window = (MainWindow)sender!; s_open.Remove(window.Root!); window.Closed -= DevToolsClosed; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs index be3564e781..16852001da 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs @@ -9,12 +9,12 @@ namespace Avalonia.Diagnostics { public IControl Build(object data) { - var name = data.GetType().FullName.Replace("ViewModel", "View"); + var name = data.GetType().FullName!.Replace("ViewModel", "View"); var type = Type.GetType(name); if (type != null) { - return (Control)Activator.CreateInstance(type); + return (Control)Activator.CreateInstance(type)!; } else { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index 19e4a702eb..ab088a208c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.ViewModels _target = o; Property = property; - if (!property.DeclaringType.IsInterface) + if (property.DeclaringType == null || !property.DeclaringType.IsInterface) { Name = property.Name; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 74709cb91a..1f7205eb16 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -250,7 +250,7 @@ namespace Avalonia.Diagnostics.ViewModels .Select(x => new ClrPropertyViewModel(o, x)); } - private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if (_propertyIndex.TryGetValue(e.Property, out var properties)) { @@ -263,9 +263,10 @@ namespace Avalonia.Diagnostics.ViewModels Layout.ControlPropertyChanged(sender, e); } - private void ControlPropertyChanged(object sender, PropertyChangedEventArgs e) + private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (_propertyIndex.TryGetValue(e.PropertyName, out var properties)) + if (e.PropertyName != null + && _propertyIndex.TryGetValue(e.PropertyName, out var properties)) { foreach (var property in properties) { @@ -279,7 +280,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private void OnClassesChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnClassesChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (!SnapshotStyles) { @@ -351,10 +352,10 @@ namespace Avalonia.Diagnostics.ViewModels { public static PropertyComparer Instance { get; } = new PropertyComparer(); - public int Compare(PropertyViewModel x, PropertyViewModel y) + public int Compare(PropertyViewModel? x, PropertyViewModel? y) { - var groupX = GroupIndex(x.Group); - var groupY = GroupIndex(y.Group); + var groupX = GroupIndex(x?.Group); + var groupY = GroupIndex(y?.Group); if (groupX != groupY) { @@ -362,11 +363,11 @@ namespace Avalonia.Diagnostics.ViewModels } else { - return string.CompareOrdinal(x.Name, y.Name); + return string.CompareOrdinal(x?.Name, y?.Name); } } - private int GroupIndex(string group) + private int GroupIndex(string? group) { switch (group) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs index f691698488..4dc0c34c0a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -179,7 +179,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - public void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + public void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { try { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index f6f57b769d..65fd81cc78 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -59,14 +59,14 @@ namespace Avalonia.Diagnostics.ViewModels } } - private void HandleEvent(object sender, RoutedEventArgs e) + private void HandleEvent(object? sender, RoutedEventArgs e) { if (!_isRegistered || IsEnabled == false) return; if (sender is IVisual v && BelongsToDevTool(v)) return; - var s = sender; + var s = sender!; var handled = e.Handled; var route = e.Route; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs index e592c89892..5b27236f2e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs @@ -110,9 +110,10 @@ namespace Avalonia.Diagnostics.ViewModels } } - public IEnumerable GetErrors(string propertyName) + public IEnumerable GetErrors(string? propertyName) { - if (_errors.TryGetValue(propertyName, out var error)) + if (propertyName != null + && _errors.TryGetValue(propertyName, out var error)) { yield return error; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 4a945c253f..98afafe7d4 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -190,7 +190,7 @@ namespace Avalonia.Diagnostics.ViewModels FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; } - private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e) + private void KeyboardPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 779d74d554..bfd098985a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -31,13 +31,13 @@ namespace Avalonia.Diagnostics.ViewModels if (!converter.CanConvertTo(typeof(string)) || converter.GetType() == typeof(CollectionConverter)) { - return value.ToString(); + return value.ToString() ?? "(null)"; } return converter.ConvertToString(value); } - private static object InvokeParse(string s, Type targetType) + private static object? InvokeParse(string s, Type targetType) { var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); @@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels throw new InvalidCastException("Unable to convert value."); } - protected static object ConvertFromString(string s, Type targetType) + protected static object? ConvertFromString(string s, Type targetType) { var converter = TypeDescriptor.GetConverter(targetType); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs index 2655b0e31f..e93dc7361b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs @@ -16,12 +16,14 @@ namespace Avalonia.Diagnostics.ViewModels public void CopyResourceKey() { - if (Key is null) + var textToCopy = Key?.ToString(); + + if (textToCopy is null) { return; } - CopyToClipboard(Key.ToString()); + CopyToClipboard(textToCopy); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs index 1f0e93d751..38cbefcb93 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs @@ -36,12 +36,14 @@ namespace Avalonia.Diagnostics.ViewModels public void CopyValue() { - if (Value is null) + var textToCopy = Value?.ToString(); + + if (textToCopy is null) { return; } - CopyToClipboard(Value.ToString()); + CopyToClipboard(textToCopy); } public void CopyPropertyName() diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs index e01258f4cc..06a6fa9a74 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs @@ -35,7 +35,7 @@ namespace Avalonia.Diagnostics.ViewModels protected TreeNode Owner { get; } - public event NotifyCollectionChangedEventHandler CollectionChanged + public event NotifyCollectionChangedEventHandler? CollectionChanged { add { @@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - public event PropertyChangedEventHandler PropertyChanged + public event PropertyChangedEventHandler? PropertyChanged { add { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs index 0bc44d5778..a2ee37c625 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs @@ -10,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels private PropertyChangedEventHandler? _propertyChanged; private List events = new List(); - public event PropertyChangedEventHandler PropertyChanged + public event PropertyChangedEventHandler? PropertyChanged { add { _propertyChanged += value; events.Add("added"); } remove { _propertyChanged -= value; events.Add("removed"); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs index a018e4180e..ab523fb75a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs @@ -30,15 +30,15 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private void HistoryChanged(object sender, NotifyCollectionChangedEventArgs e) + private void HistoryChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems[0] is IControl control) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems?[0] is IControl control) { DispatcherTimer.RunOnce(control.BringIntoView, TimeSpan.Zero); } } - private void InputKeyDown(object sender, KeyEventArgs e) + private void InputKeyDown(object? sender, KeyEventArgs e) { var vm = (ConsoleViewModel?)DataContext; if (vm is null) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs index 687a20c5f6..ba7ab41e35 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs @@ -53,7 +53,7 @@ namespace Avalonia.Diagnostics.Views } } - private void OnRecordedEventsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnRecordedEventsChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (sender is ObservableCollection events) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs index f30973fea9..b688ad7676 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs @@ -58,7 +58,7 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private void PreviewKeyDown(object sender, KeyEventArgs e) + private void PreviewKeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Escape) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index fdadf114c9..bbb8e76551 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -114,6 +114,6 @@ namespace Avalonia.Diagnostics.Views } } - private void RootClosed(object sender, EventArgs e) => Close(); + private void RootClosed(object? sender, EventArgs e) => Close(); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 6ad6533e7a..3543b1adea 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -38,9 +38,9 @@ namespace Avalonia.Diagnostics.Views }; } - protected void AddAdorner(object sender, PointerEventArgs e) + protected void AddAdorner(object? sender, PointerEventArgs e) { - var node = (TreeNode?)((Control)sender).DataContext; + var node = (TreeNode?)((Control)sender!).DataContext; var vm = (TreePageViewModel?)DataContext; if (node is null || vm is null) { @@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.Views return new Thickness(-input.Left, -input.Top, -input.Right, -input.Bottom); } - protected void RemoveAdorner(object sender, PointerEventArgs e) + protected void RemoveAdorner(object? sender, PointerEventArgs e) { foreach (var border in _adorner.Children.OfType()) { @@ -97,15 +97,15 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private void TreeViewItemMaterialized(object sender, ItemContainerEventArgs e) + private void TreeViewItemMaterialized(object? sender, ItemContainerEventArgs e) { var item = (TreeViewItem)e.Containers[0].ContainerControl; item.TemplateApplied += TreeViewItemTemplateApplied; } - private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e) + private void TreeViewItemTemplateApplied(object? sender, TemplateAppliedEventArgs e) { - var item = (TreeViewItem)sender; + var item = (TreeViewItem)sender!; // This depends on the default tree item template. // We want to handle events in the item header but exclude events coming from children. From 23b4168cbc5553a4c7b63c73fd5202546a497c59 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 23:57:16 -0400 Subject: [PATCH 07/56] Temporary do not use MemberNotNull --- .../ViewModels/AvaloniaPropertyViewModel.cs | 7 +-- .../ViewModels/ClrPropertyViewModel.cs | 8 +-- .../Diagnostics/ViewModels/MainViewModel.cs | 8 +-- .../ViewModels/TreeNodeCollection.cs | 50 ++++--------------- 4 files changed, 23 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 21265f2cfc..63f68501a7 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -1,5 +1,3 @@ -using System.Diagnostics.CodeAnalysis; - namespace Avalonia.Diagnostics.ViewModels { internal class AvaloniaPropertyViewModel : PropertyViewModel @@ -10,7 +8,10 @@ namespace Avalonia.Diagnostics.ViewModels private string _priority; private string _group; +#nullable disable + // Remove "nullable disable" after MemberNotNull will work on our CI. public AvaloniaPropertyViewModel(AvaloniaObject o, AvaloniaProperty property) +#nullable restore { _target = o; Property = property; @@ -47,7 +48,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Group => _group; - [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))] + // [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))] public override void Update() { if (Property.IsDirect) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index ab088a208c..6b2dbb7bae 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; +using System.Reflection; namespace Avalonia.Diagnostics.ViewModels { @@ -9,7 +8,10 @@ namespace Avalonia.Diagnostics.ViewModels private string _type; private object? _value; +#nullable disable + // Remove "nullable disable" after MemberNotNull will work on our CI. public ClrPropertyViewModel(object o, PropertyInfo property) +#nullable restore { _target = o; Property = property; @@ -47,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - [MemberNotNull(nameof(_type))] + // [MemberNotNull(nameof(_type))] public override void Update() { var val = Property.GetValue(_target); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 98afafe7d4..72491bebc2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using Avalonia.Controls; using Avalonia.Diagnostics.Models; @@ -24,7 +23,10 @@ namespace Avalonia.Diagnostics.ViewModels private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; +#nullable disable + // Remove "nullable disable" after MemberNotNull will work on our CI. public MainViewModel(TopLevel root) +#nullable restore { _root = root; _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); @@ -85,7 +87,7 @@ namespace Avalonia.Diagnostics.ViewModels public ViewModelBase Content { get { return _content; } - [MemberNotNull(nameof(_content))] + // [MemberNotNull(nameof(_content))] private set { if (_content is TreePageViewModel oldTree && @@ -116,7 +118,7 @@ namespace Avalonia.Diagnostics.ViewModels public int SelectedTab { get { return _selectedTab; } - [MemberNotNull(nameof(_content))] + // [MemberNotNull(nameof(_content))] set { _selectedTab = value; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs index 06a6fa9a74..c007411f49 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using Avalonia.Collections; @@ -15,52 +14,22 @@ namespace Avalonia.Diagnostics.ViewModels public TreeNodeCollection(TreeNode owner) => Owner = owner; - public TreeNode this[int index] - { - get - { - EnsureInitialized(); - return _inner[index]; - } - } + public TreeNode this[int index] => EnsureInitialized()[index]; - public int Count - { - get - { - EnsureInitialized(); - return _inner.Count; - } - } + public int Count => EnsureInitialized().Count; protected TreeNode Owner { get; } public event NotifyCollectionChangedEventHandler? CollectionChanged { - add - { - EnsureInitialized(); - _inner.CollectionChanged += value; - } - remove - { - EnsureInitialized(); - _inner.CollectionChanged -= value; - } + add => EnsureInitialized().CollectionChanged += value; + remove => EnsureInitialized().CollectionChanged -= value; } public event PropertyChangedEventHandler? PropertyChanged { - add - { - EnsureInitialized(); - _inner.PropertyChanged += value; - } - remove - { - EnsureInitialized(); - _inner.PropertyChanged -= value; - } + add => EnsureInitialized().PropertyChanged += value; + remove => EnsureInitialized().PropertyChanged -= value; } public virtual void Dispose() @@ -76,22 +45,21 @@ namespace Avalonia.Diagnostics.ViewModels public IEnumerator GetEnumerator() { - EnsureInitialized(); - return _inner.GetEnumerator(); + return EnsureInitialized().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); protected abstract void Initialize(AvaloniaList nodes); - [MemberNotNull(nameof(_inner))] - private void EnsureInitialized() + private AvaloniaList EnsureInitialized() { if (_inner is null) { _inner = new AvaloniaList(); Initialize(_inner); } + return _inner; } } } From 4b2852f0e11e22f80a59b4e5182be5e0e090b2db Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 4 Jun 2021 14:08:30 +0200 Subject: [PATCH 08/56] Fix light dismiss overlay intercepting titlebar hit tests. --- src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index d886b67241..8a340aac5e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -117,7 +117,7 @@ namespace Avalonia.Win32 { var visual = window.Renderer.HitTestFirst(position, _owner as Window, x => { - if (x is IInputElement ie && !ie.IsHitTestVisible) + if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsVisible)) { return false; } From 471aa9c7779c6d0e29474d34dc1c885863799021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Sun, 6 Jun 2021 15:06:49 +0200 Subject: [PATCH 09/56] fix xaml errors in filtertextbox --- .../Diagnostics/Controls/FilterTextBox.axaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml index 52720e652f..b7995c38e3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml @@ -27,7 +27,9 @@ - M7.495 9.052L8.386 11.402H9.477L6.237 3H5.217L2 11.402H3.095L3.933 9.052H7.495ZM5.811 4.453L5.855 4.588L7.173 8.162H4.255L5.562 4.588L5.606 4.453L5.644 4.297L5.676 4.145L5.697 4.019H5.72L5.744 4.145L5.773 4.297L5.811 4.453ZM13.795 10.464V11.4H14.755V7.498C14.755 6.779 14.575 6.226 14.216 5.837C13.857 5.448 13.327 5.254 12.628 5.254C12.429 5.254 12.227 5.273 12.022 5.31C11.817 5.347 11.622 5.394 11.439 5.451C11.256 5.508 11.091 5.569 10.944 5.636C10.797 5.703 10.683 5.765 10.601 5.824V6.808C10.867 6.578 11.167 6.397 11.505 6.268C11.843 6.139 12.194 6.075 12.557 6.075C12.745 6.075 12.915 6.103 13.07 6.16C13.225 6.217 13.357 6.306 13.466 6.427C13.575 6.548 13.659 6.706 13.718 6.899C13.777 7.092 13.806 7.326 13.806 7.599L11.995 7.851C11.651 7.898 11.355 7.977 11.107 8.088C10.859 8.199 10.654 8.339 10.492 8.507C10.33 8.675 10.21 8.868 10.132 9.087C10.054 9.306 10.015 9.546 10.015 9.808C10.015 10.054 10.057 10.283 10.139 10.496C10.221 10.709 10.342 10.893 10.502 11.047C10.662 11.201 10.862 11.323 11.1 11.413C11.338 11.503 11.613 11.548 11.926 11.548C12.328 11.548 12.686 11.456 13.001 11.27C13.316 11.084 13.573 10.816 13.772 10.464H13.795ZM11.667 8.721C11.843 8.657 12.068 8.607 12.341 8.572L13.806 8.367V8.976C13.806 9.222 13.765 9.451 13.683 9.664C13.601 9.877 13.486 10.063 13.34 10.221C13.194 10.379 13.019 10.503 12.816 10.593C12.613 10.683 12.39 10.728 12.148 10.728C11.961 10.728 11.795 10.703 11.653 10.652C11.511 10.601 11.392 10.53 11.296 10.441C11.2 10.352 11.127 10.247 11.076 10.125C11.025 10.003 11 9.873 11 9.732C11 9.568 11.018 9.421 11.055 9.292C11.092 9.163 11.16 9.051 11.257 8.958C11.354 8.865 11.491 8.785 11.667 8.721Z + + M7.495 9.052L8.386 11.402H9.477L6.237 3H5.217L2 11.402H3.095L3.933 9.052H7.495ZM5.811 4.453L5.855 4.588L7.173 8.162H4.255L5.562 4.588L5.606 4.453L5.644 4.297L5.676 4.145L5.697 4.019H5.72L5.744 4.145L5.773 4.297L5.811 4.453ZM13.795 10.464V11.4H14.755V7.498C14.755 6.779 14.575 6.226 14.216 5.837C13.857 5.448 13.327 5.254 12.628 5.254C12.429 5.254 12.227 5.273 12.022 5.31C11.817 5.347 11.622 5.394 11.439 5.451C11.256 5.508 11.091 5.569 10.944 5.636C10.797 5.703 10.683 5.765 10.601 5.824V6.808C10.867 6.578 11.167 6.397 11.505 6.268C11.843 6.139 12.194 6.075 12.557 6.075C12.745 6.075 12.915 6.103 13.07 6.16C13.225 6.217 13.357 6.306 13.466 6.427C13.575 6.548 13.659 6.706 13.718 6.899C13.777 7.092 13.806 7.326 13.806 7.599L11.995 7.851C11.651 7.898 11.355 7.977 11.107 8.088C10.859 8.199 10.654 8.339 10.492 8.507C10.33 8.675 10.21 8.868 10.132 9.087C10.054 9.306 10.015 9.546 10.015 9.808C10.015 10.054 10.057 10.283 10.139 10.496C10.221 10.709 10.342 10.893 10.502 11.047C10.662 11.201 10.862 11.323 11.1 11.413C11.338 11.503 11.613 11.548 11.926 11.548C12.328 11.548 12.686 11.456 13.001 11.27C13.316 11.084 13.573 10.816 13.772 10.464H13.795ZM11.667 8.721C11.843 8.657 12.068 8.607 12.341 8.572L13.806 8.367V8.976C13.806 9.222 13.765 9.451 13.683 9.664C13.601 9.877 13.486 10.063 13.34 10.221C13.194 10.379 13.019 10.503 12.816 10.593C12.613 10.683 12.39 10.728 12.148 10.728C11.961 10.728 11.795 10.703 11.653 10.652C11.511 10.601 11.392 10.53 11.296 10.441C11.2 10.352 11.127 10.247 11.076 10.125C11.025 10.003 11 9.873 11 9.732C11 9.568 11.018 9.421 11.055 9.292C11.092 9.163 11.16 9.051 11.257 8.958C11.354 8.865 11.491 8.785 11.667 8.721Z + @@ -38,7 +40,9 @@ - M1 2H15V3H1V2ZM14 4H13V12H14V4ZM11.272 8.387C11.194 8.088 11.073 7.825 10.912 7.601C10.751 7.377 10.547 7.2 10.303 7.071C10.059 6.942 9.769 6.878 9.437 6.878C9.239 6.878 9.057 6.902 8.89 6.951C8.725 7 8.574 7.068 8.437 7.156C8.301 7.244 8.18 7.35 8.072 7.474L7.893 7.732V4.578H7V12H7.893V11.425L8.019 11.6C8.106 11.702 8.208 11.79 8.323 11.869C8.44 11.947 8.572 12.009 8.721 12.055C8.87 12.101 9.035 12.123 9.219 12.123C9.572 12.123 9.885 12.052 10.156 11.911C10.428 11.768 10.655 11.573 10.838 11.325C11.021 11.075 11.159 10.782 11.252 10.446C11.345 10.108 11.392 9.743 11.392 9.349C11.391 9.007 11.352 8.686 11.272 8.387ZM9.793 7.78C9.944 7.851 10.075 7.956 10.183 8.094C10.292 8.234 10.377 8.407 10.438 8.611C10.489 8.785 10.52 8.982 10.527 9.198L10.52 9.323C10.52 9.65 10.487 9.943 10.42 10.192C10.353 10.438 10.259 10.645 10.142 10.806C10.025 10.968 9.882 11.091 9.721 11.172C9.399 11.334 8.961 11.338 8.652 11.187C8.499 11.112 8.366 11.012 8.259 10.891C8.174 10.795 8.103 10.675 8.041 10.524C8.041 10.524 7.862 10.077 7.862 9.577C7.862 9.077 8.041 8.575 8.041 8.575C8.103 8.398 8.177 8.257 8.265 8.145C8.379 8.002 8.521 7.886 8.689 7.8C8.857 7.714 9.054 7.671 9.276 7.671C9.466 7.671 9.64 7.708 9.793 7.78ZM15 13H1V14H15V13ZM2.813 10L2.085 12.031H1L1.025 11.959L3.466 4.87305H4.407L6.892 12.031H5.81L5.032 10H2.813ZM3.934 6.42205H3.912L3.007 9.17505H4.848L3.934 6.42205Z + + M1 2H15V3H1V2ZM14 4H13V12H14V4ZM11.272 8.387C11.194 8.088 11.073 7.825 10.912 7.601C10.751 7.377 10.547 7.2 10.303 7.071C10.059 6.942 9.769 6.878 9.437 6.878C9.239 6.878 9.057 6.902 8.89 6.951C8.725 7 8.574 7.068 8.437 7.156C8.301 7.244 8.18 7.35 8.072 7.474L7.893 7.732V4.578H7V12H7.893V11.425L8.019 11.6C8.106 11.702 8.208 11.79 8.323 11.869C8.44 11.947 8.572 12.009 8.721 12.055C8.87 12.101 9.035 12.123 9.219 12.123C9.572 12.123 9.885 12.052 10.156 11.911C10.428 11.768 10.655 11.573 10.838 11.325C11.021 11.075 11.159 10.782 11.252 10.446C11.345 10.108 11.392 9.743 11.392 9.349C11.391 9.007 11.352 8.686 11.272 8.387ZM9.793 7.78C9.944 7.851 10.075 7.956 10.183 8.094C10.292 8.234 10.377 8.407 10.438 8.611C10.489 8.785 10.52 8.982 10.527 9.198L10.52 9.323C10.52 9.65 10.487 9.943 10.42 10.192C10.353 10.438 10.259 10.645 10.142 10.806C10.025 10.968 9.882 11.091 9.721 11.172C9.399 11.334 8.961 11.338 8.652 11.187C8.499 11.112 8.366 11.012 8.259 10.891C8.174 10.795 8.103 10.675 8.041 10.524C8.041 10.524 7.862 10.077 7.862 9.577C7.862 9.077 8.041 8.575 8.041 8.575C8.103 8.398 8.177 8.257 8.265 8.145C8.379 8.002 8.521 7.886 8.689 7.8C8.857 7.714 9.054 7.671 9.276 7.671C9.466 7.671 9.64 7.708 9.793 7.78ZM15 13H1V14H15V13ZM2.813 10L2.085 12.031H1L1.025 11.959L3.466 4.87305H4.407L6.892 12.031H5.81L5.032 10H2.813ZM3.934 6.42205H3.912L3.007 9.17505H4.848L3.934 6.42205Z + @@ -49,7 +53,9 @@ - M10.0122 2H10.9879V5.11346L13.5489 3.55609L14.034 4.44095L11.4702 6L14.034 7.55905L13.5489 8.44391L10.9879 6.88654V10H10.0122V6.88654L7.45114 8.44391L6.96606 7.55905L9.5299 6L6.96606 4.44095L7.45114 3.55609L10.0122 5.11346V2ZM2 10H6V14H2V10Z + + M10.0122 2H10.9879V5.11346L13.5489 3.55609L14.034 4.44095L11.4702 6L14.034 7.55905L13.5489 8.44391L10.9879 6.88654V10H10.0122V6.88654L7.45114 8.44391L6.96606 7.55905L9.5299 6L6.96606 4.44095L7.45114 3.55609L10.0122 5.11346V2ZM2 10H6V14H2V10Z + From dbe9a2e2d17cbcd8ba5e1ea960b5e74a3564a31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Sun, 6 Jun 2021 15:07:13 +0200 Subject: [PATCH 10/56] fix xaml errors in controlcatalog sample --- samples/ControlCatalog/Pages/ContextFlyoutPage.axaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml b/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml index e15637aa0f..f0e079ad91 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml @@ -84,13 +84,13 @@ - - - From 12c37270eeedb6fecf620f06a74e85b60514f3ac Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 12:27:14 -0400 Subject: [PATCH 11/56] SelectedDate and SelectedTime => TwoWay by default Fixes #3407 --- src/Avalonia.Controls/DateTimePickers/DatePicker.cs | 4 +++- src/Avalonia.Controls/DateTimePickers/TimePicker.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 8d893154eb..43bc7d1df9 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Interactivity; using System; using System.Collections.Generic; @@ -88,7 +89,8 @@ namespace Avalonia.Controls /// public static readonly DirectProperty SelectedDateProperty = AvaloniaProperty.RegisterDirect(nameof(SelectedDate), - x => x.SelectedDate, (x, v) => x.SelectedDate = v); + x => x.SelectedDate, (x, v) => x.SelectedDate = v, + defaultBindingMode: BindingMode.TwoWay); // Template Items private Button _flyoutButton; diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index d0cf772c01..6b3f66912f 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Data; using System; using System.Globalization; @@ -44,7 +45,8 @@ namespace Avalonia.Controls /// public static readonly DirectProperty SelectedTimeProperty = AvaloniaProperty.RegisterDirect(nameof(SelectedTime), - x => x.SelectedTime, (x, v) => x.SelectedTime = v); + x => x.SelectedTime, (x, v) => x.SelectedTime = v, + defaultBindingMode: BindingMode.TwoWay); // Template Items private TimePickerPresenter _presenter; From 6763337e25befce9c77e1a51da2cf57556fbfd0e Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 12:45:53 -0400 Subject: [PATCH 12/56] Add IsUndoEnabled to TextBox --- src/Avalonia.Controls/TextBox.cs | 87 +++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 3c221cbf27..e38531e540 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -119,9 +119,9 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(RevealPassword)); public static readonly DirectProperty CanCutProperty = - AvaloniaProperty.RegisterDirect( - nameof(CanCut), - o => o.CanCut); + AvaloniaProperty.RegisterDirect( + nameof(CanCut), + o => o.CanCut); public static readonly DirectProperty CanCopyProperty = AvaloniaProperty.RegisterDirect( @@ -129,9 +129,14 @@ namespace Avalonia.Controls o => o.CanCopy); public static readonly DirectProperty CanPasteProperty = - AvaloniaProperty.RegisterDirect( - nameof(CanPaste), - o => o.CanPaste); + AvaloniaProperty.RegisterDirect( + nameof(CanPaste), + o => o.CanPaste); + + public static readonly StyledProperty IsUndoEnabledProperty = + AvaloniaProperty.Register( + nameof(IsUndoEnabled), + defaultValue: true); struct UndoRedoState : IEquatable { @@ -218,7 +223,7 @@ namespace Avalonia.Controls value = CoerceCaretIndex(value); SetAndRaise(CaretIndexProperty, ref _caretIndex, value); UndoRedoState state; - if (_undoRedoHelper.TryGetLastState(out state) && state.Text == Text) + if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text) _undoRedoHelper.UpdateLastState(); } } @@ -316,7 +321,7 @@ namespace Avalonia.Controls SelectionEnd = CoerceCaretIndex(SelectionEnd, value); CaretIndex = CoerceCaretIndex(caretIndex, value); - if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing) + if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) { _undoRedoHelper.Clear(); } @@ -329,7 +334,7 @@ namespace Avalonia.Controls get { return GetSelection(); } set { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (string.IsNullOrEmpty(value)) { DeleteSelection(); @@ -338,7 +343,7 @@ namespace Avalonia.Controls { HandleTextInput(value); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } } @@ -446,6 +451,27 @@ namespace Avalonia.Controls private set { SetAndRaise(CanPasteProperty, ref _canPaste, value); } } + /// + /// Property for determining whether undo/redo is enabled + /// + public bool IsUndoEnabled + { + get { return GetValue(IsUndoEnabledProperty); } + set + { + SetValue(IsUndoEnabledProperty, value); + if (value == false) + { + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting this property to false clears the undo stack. + // Therefore, if you disable undo and then re-enable it, undo commands still do not work + // because the undo stack was emptied when you disabled undo." + _undoRedoHelper.Clear(); + } + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -551,7 +577,10 @@ namespace Avalonia.Controls SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); CaretIndex += input.Length; ClearSelection(); - _undoRedoHelper.DiscardRedo(); + if (IsUndoEnabled) + { + _undoRedoHelper.DiscardRedo(); + } } } @@ -570,10 +599,10 @@ namespace Avalonia.Controls var text = GetSelection(); if (text is null) return; - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); Copy(); DeleteSelection(); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } public async void Copy() @@ -591,9 +620,9 @@ namespace Avalonia.Controls if (text is null) return; - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput(text); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } protected override void OnKeyDown(KeyEventArgs e) @@ -638,7 +667,7 @@ namespace Avalonia.Controls Paste(); handled = true; } - else if (Match(keymap.Undo)) + else if (Match(keymap.Undo) && IsUndoEnabled) { try { @@ -652,7 +681,7 @@ namespace Avalonia.Controls handled = true; } - else if (Match(keymap.Redo)) + else if (Match(keymap.Redo) && IsUndoEnabled) { try { @@ -752,7 +781,7 @@ namespace Avalonia.Controls break; case Key.Back: - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (hasWholeWordModifiers && SelectionStart == SelectionEnd) { SetSelectionForControlBackspace(); @@ -776,13 +805,13 @@ namespace Avalonia.Controls CaretIndex -= removedCharacters; ClearSelection(); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; break; case Key.Delete: - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (hasWholeWordModifiers && SelectionStart == SelectionEnd) { SetSelectionForControlDelete(); @@ -804,7 +833,7 @@ namespace Avalonia.Controls SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters)); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; break; @@ -812,9 +841,9 @@ namespace Avalonia.Controls case Key.Enter: if (AcceptsReturn) { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput(NewLine); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; } @@ -823,9 +852,9 @@ namespace Avalonia.Controls case Key.Tab: if (AcceptsTab) { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput("\t"); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; } else @@ -1251,5 +1280,13 @@ namespace Avalonia.Controls ClearSelection(); } } + + private void SnapshotUndoRedo() + { + if (IsUndoEnabled) + { + _undoRedoHelper.Snapshot(); + } + } } } From c6eb57c5350cf2e236f6c3c8bc26297b24d70ef7 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 13:19:46 -0400 Subject: [PATCH 13/56] Add TextBox UndoLimit --- src/Avalonia.Controls/TextBox.cs | 20 +++++++++++++++++++ src/Avalonia.Controls/Utils/UndoRedoHelper.cs | 11 ++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index e38531e540..ffe0d39cab 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -138,6 +138,13 @@ namespace Avalonia.Controls nameof(IsUndoEnabled), defaultValue: true); + public static readonly DirectProperty UndoLimitProperty = + AvaloniaProperty.RegisterDirect( + nameof(UndoLimit), + o => o._undoRedoHelper.Limit, + (o, v) => o._undoRedoHelper.Limit = v, + unsetValue: -1); + struct UndoRedoState : IEquatable { public string Text { get; } @@ -472,6 +479,19 @@ namespace Avalonia.Controls } } + public int UndoLimit + { + get { return GetValue(UndoLimitProperty); } + set + { + SetValue(UndoLimitProperty, value); + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting UndoLimit clears the undo queue." + _undoRedoHelper.Clear(); + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 17cf681f15..7374f20a0c 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -22,6 +22,10 @@ namespace Avalonia.Controls.Utils private LinkedListNode _currentNode; + /// + /// Maximum number of states this helper can store for undo/redo. + /// If -1, no limit is imposed. + /// public int Limit { get; set; } = 10; public UndoRedoHelper(IUndoRedoHost host) @@ -54,7 +58,10 @@ namespace Avalonia.Controls.Utils public bool HasState => _currentNode != null; public void UpdateLastState(TState state) { - _states.Last.Value = state; + if (_states.Last != null) + { + _states.Last.Value = state; + } } public void UpdateLastState() @@ -86,7 +93,7 @@ namespace Avalonia.Controls.Utils DiscardRedo(); _states.AddLast(current); _currentNode = _states.Last; - if (_states.Count > Limit) + if (Limit != -1 && _states.Count > Limit) _states.RemoveFirst(); } } From 4fe29b0f9e9568a85f0ced91e5041e80ee55d2c5 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 21:27:47 -0400 Subject: [PATCH 14/56] UndoLimit now uses property properly --- src/Avalonia.Controls/TextBox.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index ffe0d39cab..06745bb3bc 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -141,8 +141,8 @@ namespace Avalonia.Controls public static readonly DirectProperty UndoLimitProperty = AvaloniaProperty.RegisterDirect( nameof(UndoLimit), - o => o._undoRedoHelper.Limit, - (o, v) => o._undoRedoHelper.Limit = v, + o => o.UndoLimit, + (o, v) => o.UndoLimit = v, unsetValue: -1); struct UndoRedoState : IEquatable @@ -481,10 +481,10 @@ namespace Avalonia.Controls public int UndoLimit { - get { return GetValue(UndoLimitProperty); } + get { return _undoRedoHelper.Limit; } set { - SetValue(UndoLimitProperty, value); + _undoRedoHelper.Limit = value; // from docs at // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: // "Setting UndoLimit clears the undo queue." From b5bb89348df5bdd201664c6e3ef71a5ed13400fe Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 21:38:50 -0400 Subject: [PATCH 15/56] Handle IsUndoEnabled in property changed --- src/Avalonia.Controls/TextBox.cs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 06745bb3bc..351d648afc 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -464,19 +464,7 @@ namespace Avalonia.Controls public bool IsUndoEnabled { get { return GetValue(IsUndoEnabledProperty); } - set - { - SetValue(IsUndoEnabledProperty, value); - if (value == false) - { - // from docs at - // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: - // "Setting this property to false clears the undo stack. - // Therefore, if you disable undo and then re-enable it, undo commands still do not work - // because the undo stack was emptied when you disabled undo." - _undoRedoHelper.Clear(); - } - } + set { SetValue(IsUndoEnabledProperty, value); } } public int UndoLimit @@ -511,6 +499,15 @@ namespace Avalonia.Controls UpdatePseudoclasses(); UpdateCommandStates(); } + else if (change.Property == IsUndoEnabledProperty && change.NewValue.GetValueOrDefault() == false) + { + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting this property to false clears the undo stack. + // Therefore, if you disable undo and then re-enable it, undo commands still do not work + // because the undo stack was emptied when you disabled undo." + _undoRedoHelper.Clear(); + } } private void UpdateCommandStates() From 8ed27724793cd7315c9fd2cd73f804a9429ab1ae Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Mon, 7 Jun 2021 07:56:35 -0400 Subject: [PATCH 16/56] Fix not raising property change on UndoLimit --- src/Avalonia.Controls/TextBox.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 351d648afc..1bee15bccd 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -472,7 +472,15 @@ namespace Avalonia.Controls get { return _undoRedoHelper.Limit; } set { - _undoRedoHelper.Limit = value; + if (_undoRedoHelper.Limit != value) + { + // can't use SetAndRaise due to using _undoRedoHelper.Limit + // (can't send a ref of a property to SetAndRaise), + // so use RaisePropertyChanged instead. + var oldValue = _undoRedoHelper.Limit; + _undoRedoHelper.Limit = value; + RaisePropertyChanged(UndoLimitProperty, oldValue, value); + } // from docs at // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: // "Setting UndoLimit clears the undo queue." From 5365b2fc9564d72d01873a06f6ec79684ef0d7e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 16:04:06 +0200 Subject: [PATCH 17/56] Transitions for solid color brushes. --- samples/RenderDemo/Pages/TransitionsPage.xaml | 15 ++++++++++ .../Animation/Transitions/BrushTransition.cs | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml index f9f69fb341..ce5db380c0 100644 --- a/samples/RenderDemo/Pages/TransitionsPage.xaml +++ b/samples/RenderDemo/Pages/TransitionsPage.xaml @@ -141,6 +141,19 @@ + + + + @@ -166,6 +179,8 @@ + + diff --git a/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs new file mode 100644 index 0000000000..0f7501e279 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs @@ -0,0 +1,29 @@ +using System; +using Avalonia.Animation.Animators; +using Avalonia.Media; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// Only values of will correctly transition. + /// + public class ISolidColorBrushTransition : Transition + { + private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator(); + + public override IObservable DoTransition(IObservable progress, IBrush oldValue, IBrush newValue) + { + var oldSolidBrush = AsImmutable(oldValue); + var newSolidBrush = AsImmutable(newValue); + + return new AnimatorTransitionObservable( + s_animator, progress, Easing, oldSolidBrush, newSolidBrush); + } + + private static ISolidColorBrush AsImmutable(IBrush brush) + { + return (ISolidColorBrush)(brush as ISolidColorBrush)?.ToImmutable(); + } + } +} From c48a516ec6e30c6f2e6ebe52e0e6c06c62157c8a Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:33:55 +0200 Subject: [PATCH 18/56] Unify brush transition. Handle brush type mismatch in a nicer way. --- samples/RenderDemo/Pages/TransitionsPage.xaml | 23 +++++++- .../Animation/Transitions/BrushTransition.cs | 56 ++++++++++++++++--- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml index ce5db380c0..1985074b0f 100644 --- a/samples/RenderDemo/Pages/TransitionsPage.xaml +++ b/samples/RenderDemo/Pages/TransitionsPage.xaml @@ -145,7 +145,7 @@ + + + + @@ -181,6 +201,7 @@ + diff --git a/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs index 0f7501e279..8d0b9089f9 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs @@ -1,29 +1,67 @@ using System; using Avalonia.Animation.Animators; +using Avalonia.Animation.Easings; using Avalonia.Media; +#nullable enable + namespace Avalonia.Animation { /// /// Transition class that handles with type. - /// Only values of will correctly transition. + /// Only values of will transition correctly at the moment. /// - public class ISolidColorBrushTransition : Transition + public class BrushTransition : Transition { private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator(); - public override IObservable DoTransition(IObservable progress, IBrush oldValue, IBrush newValue) + public override IObservable DoTransition(IObservable progress, IBrush? oldValue, IBrush? newValue) + { + var oldSolidColorBrush = TryGetSolidColorBrush(oldValue); + var newSolidColorBrush = TryGetSolidColorBrush(newValue); + + if (oldSolidColorBrush != null && newSolidColorBrush != null) + { + EnsureImmutable(ref oldSolidColorBrush); + EnsureImmutable(ref newSolidColorBrush); + + return new AnimatorTransitionObservable( + s_animator, progress, Easing, oldSolidColorBrush, newSolidColorBrush); + } + + return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue); + } + + private static void EnsureImmutable(ref ISolidColorBrush brush) + { + brush = (ISolidColorBrush)brush.ToImmutable(); + } + + private static ISolidColorBrush? TryGetSolidColorBrush(IBrush? brush) { - var oldSolidBrush = AsImmutable(oldValue); - var newSolidBrush = AsImmutable(newValue); + if (brush is null) + { + return Brushes.Transparent; + } - return new AnimatorTransitionObservable( - s_animator, progress, Easing, oldSolidBrush, newSolidBrush); + return brush as ISolidColorBrush; } - private static ISolidColorBrush AsImmutable(IBrush brush) + private class IncompatibleTransitionObservable : TransitionObservableBase { - return (ISolidColorBrush)(brush as ISolidColorBrush)?.ToImmutable(); + private readonly IBrush? _from; + private readonly IBrush? _to; + + public IncompatibleTransitionObservable(IObservable progress, Easing easing, IBrush? from, IBrush? to) : base(progress, easing) + { + _from = @from; + _to = to; + } + + protected override IBrush? ProduceValue(double progress) + { + return progress < 0.5 ? _from : _to; + } } } } From f877fe5ae149c4573540ddd960c40f0ad4f93e59 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 23:59:16 +0200 Subject: [PATCH 19/56] Use immutable data to limit allocations. --- src/Avalonia.Controls/ExperimentalAcrylicBorder.cs | 3 ++- src/Avalonia.Controls/Presenters/TextPresenter.cs | 9 ++++++--- src/Avalonia.Controls/TickBar.cs | 3 ++- src/Avalonia.Controls/Utils/BorderRenderHelper.cs | 5 +++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 0a01767a07..57861163d6 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -3,6 +3,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using System; +using Avalonia.Media.Immutable; namespace Avalonia.Controls { @@ -90,7 +91,7 @@ namespace Avalonia.Controls } else { - _borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new SolidColorBrush(Material.FallbackColor), null, default); + _borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new ImmutableSolidColorBrush(Material.FallbackColor), null, default); } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 3bec46a9ac..5506ce05d6 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -6,6 +6,7 @@ using Avalonia.Metadata; using Avalonia.Threading; using Avalonia.VisualTree; using Avalonia.Layout; +using Avalonia.Media.Immutable; namespace Avalonia.Controls.Presenters { @@ -366,24 +367,26 @@ namespace Avalonia.Controls.Presenters if (caretBrush is null) { - var backgroundColor = (Background as SolidColorBrush)?.Color; + var backgroundColor = (Background as ISolidColorBrush)?.Color; if (backgroundColor.HasValue) { byte red = (byte)~(backgroundColor.Value.R); byte green = (byte)~(backgroundColor.Value.G); byte blue = (byte)~(backgroundColor.Value.B); - caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue)); + caretBrush = new ImmutableSolidColorBrush(Color.FromRgb(red, green, blue)); } else + { caretBrush = Brushes.Black; + } } if (_caretBlink) { var (p1, p2) = GetCaretPoints(); context.DrawLine( - new Pen(caretBrush, 1), + new ImmutablePen(caretBrush, 1), p1, p2); } } diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 6ea5277a55..237bc2ce1d 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -1,6 +1,7 @@ using Avalonia.Collections; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Utilities; namespace Avalonia.Controls @@ -295,7 +296,7 @@ namespace Avalonia.Controls endPoint = pt; } - var pen = new Pen(Fill, 1.0d); + var pen = new ImmutablePen(Fill?.ToImmutable(), 1.0d); // Is it Vertical? if (Placement == TickBarPlacement.Left || Placement == TickBarPlacement.Right) diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index 3128753781..f8ab58d46e 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Platform; using Avalonia.Utilities; @@ -114,9 +115,9 @@ namespace Avalonia.Controls.Utils var borderThickness = _borderThickness.Top; IPen pen = null; - if (borderThickness > 0) + if (borderBrush != null && borderThickness > 0) { - pen = new Pen(borderBrush, borderThickness); + pen = new ImmutablePen(borderBrush.ToImmutable(), borderThickness); } var rect = new Rect(_size); From 9b337d10de8fa415df3924c17455d5621cb2a9e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 9 Jun 2021 10:12:42 +0200 Subject: [PATCH 20/56] Cleanup. --- .../Animation/Transitions/BrushTransition.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs index 8d0b9089f9..cc5af1b4b1 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs @@ -22,9 +22,6 @@ namespace Avalonia.Animation if (oldSolidColorBrush != null && newSolidColorBrush != null) { - EnsureImmutable(ref oldSolidColorBrush); - EnsureImmutable(ref newSolidColorBrush); - return new AnimatorTransitionObservable( s_animator, progress, Easing, oldSolidColorBrush, newSolidColorBrush); } @@ -32,11 +29,6 @@ namespace Avalonia.Animation return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue); } - private static void EnsureImmutable(ref ISolidColorBrush brush) - { - brush = (ISolidColorBrush)brush.ToImmutable(); - } - private static ISolidColorBrush? TryGetSolidColorBrush(IBrush? brush) { if (brush is null) @@ -54,7 +46,7 @@ namespace Avalonia.Animation public IncompatibleTransitionObservable(IObservable progress, Easing easing, IBrush? from, IBrush? to) : base(progress, easing) { - _from = @from; + _from = from; _to = to; } From 6f5bf5217899f4a4e67bdbc18f3ddb1128c59317 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 10 Jun 2021 13:15:58 +0200 Subject: [PATCH 21/56] Fix problems with mutable brush being passed to an immutable pen. --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 15 ++++++--------- .../Media/Immutable/ImmutablePen.cs | 3 +++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 5506ce05d6..ff63e5644f 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -361,9 +361,9 @@ namespace Avalonia.Controls.Presenters RenderInternal(context); - if (selectionStart == selectionEnd) + if (selectionStart == selectionEnd && _caretBlink) { - var caretBrush = CaretBrush; + var caretBrush = CaretBrush?.ToImmutable(); if (caretBrush is null) { @@ -382,13 +382,10 @@ namespace Avalonia.Controls.Presenters } } - if (_caretBlink) - { - var (p1, p2) = GetCaretPoints(); - context.DrawLine( - new ImmutablePen(caretBrush, 1), - p1, p2); - } + var (p1, p2) = GetCaretPoints(); + context.DrawLine( + new ImmutablePen(caretBrush, 1), + p1, p2); } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs index 32624fbf45..3256f4b11a 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; namespace Avalonia.Media.Immutable { @@ -44,6 +45,8 @@ namespace Avalonia.Media.Immutable PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) { + Debug.Assert(!(brush is IMutableBrush)); + Brush = brush; Thickness = thickness; LineCap = lineCap; From 31d9b5b13add57f5a4b37258f2d2c0ee17dec413 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Jun 2021 16:26:38 +0200 Subject: [PATCH 22/56] Fix a couple of tests that only fail in debug mode. Introduced in #6039 due to use of `Debug.Assert`. --- tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs index c2a1a5f9e4..8c25019606 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -59,9 +59,9 @@ namespace Avalonia.Visuals.UnitTests.Media } [Fact] - public void Equality_Is_Implemented_Between_Immutable_And_Mmutable_Pens() + public void Equality_Is_Implemented_Between_Immutable_And_Mutable_Pens() { - var brush = new SolidColorBrush(Colors.Red); + var brush = new ImmutableSolidColorBrush(Colors.Red); var target1 = new ImmutablePen( brush: brush, thickness: 2, @@ -83,7 +83,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Equality_Is_Implemented_Between_Mutable_And_Immutable_DashStyles() { - var brush = new SolidColorBrush(Colors.Red); + var brush = new ImmutableSolidColorBrush(Colors.Red); var target1 = new ImmutablePen( brush: brush, thickness: 2, From 16af23a878850874cb1fc858aa3ea21af77d0ecb Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 10 Jun 2021 17:48:49 +0200 Subject: [PATCH 23/56] fixes(DevTools): Issue #5882 binding Error 'Could not find a matching property accessor for 'Priority' and 'Could not find a matching property accessor for 'IsAttached' --- .../Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs | 8 +++++--- .../Diagnostics/ViewModels/ClrPropertyViewModel.cs | 6 ++++++ .../Diagnostics/ViewModels/PropertyViewModel.cs | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 63f68501a7..e4c4ca6115 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -26,9 +26,11 @@ namespace Avalonia.Diagnostics.ViewModels public AvaloniaProperty Property { get; } public override object Key => Property; public override string Name { get; } - public bool IsAttached => Property.IsAttached; + public override bool? IsAttached => + Property.IsAttached; - public string Priority => _priority; + public override string Priority => + _priority; public override string Type => _type; @@ -69,7 +71,7 @@ namespace Avalonia.Diagnostics.ViewModels if (val != null) { RaiseAndSetIfChanged(ref _priority, val.Priority.ToString(), nameof(Priority)); - RaiseAndSetIfChanged(ref _group, IsAttached ? "Attached Properties" : "Properties", nameof(Group)); + RaiseAndSetIfChanged(ref _group, IsAttached == true ? "Attached Properties" : "Properties", nameof(Group)); } else { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index 6b2dbb7bae..65626aeea5 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -49,6 +49,12 @@ namespace Avalonia.Diagnostics.ViewModels } } + public override string Priority => + string.Empty; + + public override bool? IsAttached => + default; + // [MemberNotNull(nameof(_type))] public override void Update() { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index bfd098985a..fdbd8c1aa3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -16,7 +16,9 @@ namespace Avalonia.Diagnostics.ViewModels public abstract string Group { get; } public abstract string Type { get; } public abstract string Value { get; set; } - public abstract void Update(); + public abstract string Priority { get; } + public abstract bool? IsAttached { get; } + public abstract void Update(); protected static string ConvertToString(object? value) { From caa617fe6797dc1655c46c5462d8e8b9dc03ec2a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 10 Jun 2021 18:12:51 +0200 Subject: [PATCH 24/56] fixes(DevTools): Issue #6047 --- .../Diagnostics/ViewModels/ControlDetailsViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 1f7205eb16..3790951b0c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly IVisual _control; private readonly IDictionary> _propertyIndex; - private AvaloniaPropertyViewModel? _selectedProperty; + private PropertyViewModel? _selectedProperty; private bool _snapshotStyles; private bool _showInactiveStyles; private string? _styleStatus; @@ -139,7 +139,7 @@ namespace Avalonia.Diagnostics.ViewModels public ObservableCollection PseudoClasses { get; } - public AvaloniaPropertyViewModel? SelectedProperty + public PropertyViewModel? SelectedProperty { get => _selectedProperty; set => RaiseAndSetIfChanged(ref _selectedProperty, value); From 14ea7b44ed6c90ffbe3f5d58741e7aa545042e3c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 11 Jun 2021 18:46:13 +0200 Subject: [PATCH 25/56] Fix caret positioning with empty newlines. `FormattedTextImpl` on Skia adds a special empty line when there is a trailing newline in the string. However this line is reported as having a width due to `SKPaint.MeasureText` returning a non-zero value for `\r` and `\n`. We can't simply always use 0 width when we encounter a control char when building the rects as that breaks hit testing. Instead, mark the empty trailing line with a flag and and treat it differently when building the rects and hit-testing a range. Yes, this is a big hack but as far as I understand the whole of `FormattedTextImpl` on Skia is a bit of a hack, but until @Gillibald's new `TextPresenter` is ready, this _might_ be a good enough fix? --- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 5e630e54a6..5f4980e461 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Avalonia.Media; using Avalonia.Platform; @@ -175,7 +176,8 @@ namespace Avalonia.Skia foreach (var line in _skiaLines.Where(l => (l.Start + l.Length) > index && - lastIndex >= l.Start)) + lastIndex >= l.Start && + !l.IsEmptyTrailingLine)) { int lineEndIndex = line.Start + (line.Length > 0 ? line.Length - 1 : 0); @@ -466,7 +468,8 @@ namespace Avalonia.Skia for (int i = line.Start; i < line.Start + line.TextLength; i++) { - float w = _paint.MeasureText(Text[i].ToString()); + var c = Text[i]; + var w = line.IsEmptyTrailingLine ? 0 :_paint.MeasureText(Text[i].ToString()); _rects.Add(new Rect( prevRight, @@ -611,6 +614,7 @@ namespace Avalonia.Skia lastLine.Width = lastLineWidth; lastLine.Height = _lineHeight; lastLine.Top = curY; + lastLine.IsEmptyTrailingLine = true; _skiaLines.Add(lastLine); @@ -713,6 +717,7 @@ namespace Avalonia.Skia public int TextLength; public float Top; public float Width; + public bool IsEmptyTrailingLine; }; private struct FBrushRange From a2dc40a14cafbd347d4784e147c9fb57c3f658bc Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 12 Jun 2021 12:03:53 +0200 Subject: [PATCH 26/56] Allow for using ScrollViewer.AllowAutoHide as an attached property. --- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 24 +++++++++++++ src/Avalonia.Controls/ScrollViewer.cs | 36 +++++++++++++++---- src/Avalonia.Themes.Default/ListBox.xaml | 3 +- src/Avalonia.Themes.Default/TextBox.xaml | 3 +- src/Avalonia.Themes.Default/TreeView.xaml | 3 +- .../Controls/ListBox.xaml | 3 +- .../Controls/TextBox.xaml | 3 +- .../Controls/TreeView.xaml | 3 +- 8 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 173c5c1a94..ae61f8f642 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -585,6 +585,30 @@ namespace Avalonia }); } + /// + /// Subscribes to a property changed notifications for changes that originate from a + /// . + /// + /// The type of the property change sender. + /// /// The type of the property.. + /// The property changed observable. + /// + /// The method to call. The parameters are the sender and the event args. + /// + /// A disposable that can be used to terminate the subscription. + public static IDisposable AddClassHandler( + this IObservable> observable, + Action> action) where TTarget : AvaloniaObject + { + return observable.Subscribe(e => + { + if (e.Sender is TTarget target) + { + action(target, e); + } + }); + } + /// /// Subscribes to a property changed notifications for changes that originate from a /// . diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 90c4e05943..559edeb204 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -176,8 +176,10 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty AllowAutoHideProperty = - ScrollBar.AllowAutoHideProperty.AddOwner(); + public static readonly AttachedProperty AllowAutoHideProperty = + AvaloniaProperty.RegisterAttached( + nameof(AllowAutoHide), + true); /// /// Defines the event. @@ -207,8 +209,8 @@ namespace Avalonia.Controls /// static ScrollViewer() { - HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); - VerticalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); + HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); + VerticalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); } /// @@ -526,6 +528,26 @@ namespace Avalonia.Controls return control.GetValue(VerticalScrollBarVisibilityProperty); } + /// + /// Gets the value of the AllowAutoHideProperty attached property. + /// + /// The control to set the value on. + /// The value of the property. + public static void SetAllowAutoHide(Control control, bool value) + { + control.SetValue(AllowAutoHideProperty, value); + } + + /// + /// Gets the value of the AllowAutoHideProperty attached property. + /// + /// The control to read the value from. + /// The value of the property. + public static bool GetAllowAutoHide(Control control) + { + return control.GetValue(AllowAutoHideProperty); + } + /// /// Gets the value of the VerticalScrollBarVisibility attached property. /// @@ -604,10 +626,10 @@ namespace Avalonia.Controls CalculatedPropertiesChanged(); } - private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e) + private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e) { - var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue); - var isEnabled = !ScrollBarVisibility.Disabled.Equals(e.NewValue); + var wasEnabled = e.OldValue.GetValueOrDefault() != ScrollBarVisibility.Disabled; + var isEnabled = e.NewValue.GetValueOrDefault() != ScrollBarVisibility.Disabled; if (wasEnabled != isEnabled) { diff --git a/src/Avalonia.Themes.Default/ListBox.xaml b/src/Avalonia.Themes.Default/ListBox.xaml index e91d8a6772..1ad996a1a0 100644 --- a/src/Avalonia.Themes.Default/ListBox.xaml +++ b/src/Avalonia.Themes.Default/ListBox.xaml @@ -13,7 +13,8 @@ + VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" + AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> + VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" + AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> + VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" + AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> + VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" + AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> + VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" + AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> + VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" + AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> Date: Sat, 12 Jun 2021 22:42:59 +0200 Subject: [PATCH 27/56] Add PolyLineSegment path segment --- src/Avalonia.Visuals/Media/PolyLineSegment.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Avalonia.Visuals/Media/PolyLineSegment.cs diff --git a/src/Avalonia.Visuals/Media/PolyLineSegment.cs b/src/Avalonia.Visuals/Media/PolyLineSegment.cs new file mode 100644 index 0000000000..55bfb33041 --- /dev/null +++ b/src/Avalonia.Visuals/Media/PolyLineSegment.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using Avalonia.Collections; + +namespace Avalonia.Media +{ + /// + /// Represents a set of line segments defined by a points collection with each Point specifying the end point of a line segment. + /// + public sealed class PolyLineSegment : PathSegment + { + /// + /// Defines the property. + /// + public static readonly StyledProperty PointsProperty + = AvaloniaProperty.Register(nameof(Points)); + + /// + /// Gets or sets the points. + /// + /// + /// The points. + /// + public AvaloniaList Points + { + get => GetValue(PointsProperty); + set => SetValue(PointsProperty, value); + } + + /// + /// Initializes a new instance of the class. + /// + public PolyLineSegment() + { + Points = new Points(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The points. + public PolyLineSegment(IEnumerable points) : this() + { + Points.AddRange(points); + } + + protected internal override void ApplyTo(StreamGeometryContext ctx) + { + var points = Points; + if (points.Count > 0) + { + for (int i = 0; i < points.Count; i++) + { + ctx.LineTo(points[i]); + } + } + } + + public override string ToString() + => Points.Count >= 1 ? "L " + string.Join(" ", Points) : ""; + } +} From d6d87b1a32c91bbaa9c066b95d20350bee047ad1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 14 Jun 2021 12:31:45 +0200 Subject: [PATCH 28/56] Fix fetching neutral value in animations. Animators fetched the neutral value when the animation is instantiated but did not update the neutral value if it's updated after that. #6063 was caused by the fact that the neutral value does not take effect until the batch update due to styling has finished on the control, which is _after_ the animation has been instantiated. Listen for changes on the property and if the change is not an animated value change, update the neutral value. This isn't perfect because it won't react to changes to the neutral value while the animation is actually running and producing values because `PropertyChanged` events don't get fired for non-active values: for that we'd need to hook into `OnPropertyChangedCore`. But this at least fixes the simple case of the initial neutral value. --- src/Avalonia.Animation/AnimationInstance`1.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 6f601a3e13..0e1882ce75 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -5,6 +5,7 @@ using Avalonia.Animation.Animators; using Avalonia.Animation.Utils; using Avalonia.Data; using Avalonia.Reactive; +using JetBrains.Annotations; namespace Avalonia.Animation { @@ -45,8 +46,9 @@ namespace Avalonia.Animation _onCompleteAction = OnComplete; _interpolator = Interpolator; _baseClock = baseClock; - _neutralValue = (T)_targetControl.GetValue(_animator.Property); + control.PropertyChanged += ControlPropertyChanged; + UpdateNeutralValue(); FetchProperties(); } @@ -216,5 +218,22 @@ namespace Avalonia.Animation } } } + + private void UpdateNeutralValue() + { + var property = _animator.Property; + var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue); + + _neutralValue = baseValue != AvaloniaProperty.UnsetValue ? + (T)baseValue : (T)_targetControl.GetValue(property); + } + + private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation) + { + UpdateNeutralValue(); + } + } } } From 6b94cc4ed99dd9b2ee86b2396da5d9a7a6e788ab Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 14 Jun 2021 12:56:22 +0200 Subject: [PATCH 29/56] Added failing test for #5054. --- .../KeyboardDeviceTests.cs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs index df0a077c7f..5b39e23ba6 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs @@ -1,5 +1,9 @@ -using Avalonia.Input.Raw; +using System; +using System.Windows.Input; +using Avalonia.Controls; +using Avalonia.Input.Raw; using Avalonia.Interactivity; +using Avalonia.UnitTests; using Moq; using Xunit; @@ -86,5 +90,38 @@ namespace Avalonia.Input.UnitTests focused.Verify(x => x.RaiseEvent(It.IsAny())); } + + [Fact] + public void Can_Change_KeyBindings_In_Keybinding_Event_Handler() + { + var target = new KeyboardDevice(); + var button = new Button(); + var root = new TestRoot(button); + + button.KeyBindings.Add(new KeyBinding + { + Gesture = new KeyGesture(Key.O, KeyModifiers.Control), + Command = new DelegateCommand(() => button.KeyBindings.Clear()), + }); + + target.SetFocusedElement(button, NavigationMethod.Pointer, 0); + target.ProcessRawEvent( + new RawKeyEventArgs( + target, + 0, + root, + RawKeyEventType.KeyDown, + Key.O, + RawInputModifiers.Control)); + } + + private class DelegateCommand : ICommand + { + private readonly Action _action; + public DelegateCommand(Action action) => _action = action; + public event EventHandler CanExecuteChanged; + public bool CanExecute(object parameter) => true; + public void Execute(object parameter) => _action(); + } } } From 4285c3d0d1981993eda9fd9fe977e87fdcf5acf0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 14 Jun 2021 13:04:56 +0200 Subject: [PATCH 30/56] Don't create a copy of the array unless necessary. --- src/Avalonia.Input/KeyboardDevice.cs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index bf2f689785..a159b19026 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -218,15 +218,28 @@ namespace Avalonia.Input var bindings = (currentHandler as IInputElement)?.KeyBindings; if (bindings != null) { - // Create a copy of the KeyBindings list. + KeyBinding[]? bindingsCopy = null; + + // Create a copy of the KeyBindings list if there's a binding which matches the event. // If we don't do this the foreach loop will throw an InvalidOperationException when the KeyBindings list is changed. // This can happen when a new view is loaded which adds its own KeyBindings to the handler. - var cpy = bindings.ToArray(); - foreach (var binding in cpy) + foreach (var binding in bindings) { - if (ev.Handled) + if (binding.Gesture?.Matches(ev) == true) + { + bindingsCopy = bindings.ToArray(); break; - binding.TryHandle(ev); + } + } + + if (bindingsCopy is object) + { + foreach (var binding in bindingsCopy) + { + if (ev.Handled) + break; + binding.TryHandle(ev); + } } } currentHandler = currentHandler.VisualParent; From a11270b07e5edc062bbe37b4b356598bf574c0c7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 14 Jun 2021 13:07:28 +0200 Subject: [PATCH 31/56] Add a sanity check to the test. --- tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs index 5b39e23ba6..7730cee78c 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs @@ -97,11 +97,16 @@ namespace Avalonia.Input.UnitTests var target = new KeyboardDevice(); var button = new Button(); var root = new TestRoot(button); + var raised = 0; button.KeyBindings.Add(new KeyBinding { Gesture = new KeyGesture(Key.O, KeyModifiers.Control), - Command = new DelegateCommand(() => button.KeyBindings.Clear()), + Command = new DelegateCommand(() => + { + button.KeyBindings.Clear(); + ++raised; + }), }); target.SetFocusedElement(button, NavigationMethod.Pointer, 0); @@ -113,6 +118,8 @@ namespace Avalonia.Input.UnitTests RawKeyEventType.KeyDown, Key.O, RawInputModifiers.Control)); + + Assert.Equal(1, raised); } private class DelegateCommand : ICommand From 82754cb1633eb2d93fd29fd6ef10962267e2b9c9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 14 Jun 2021 15:29:33 +0200 Subject: [PATCH 32/56] Fixed gradient brush target rectangles. When drawn via the `DrawingContext` gradient brushes were not taking the origin of the target rect into account, only the size. Fixes #5947. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 47 +++++++++--------- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 4 +- .../Media/AvaloniaTextRenderer.cs | 4 +- .../Media/DrawingContextImpl.cs | 32 ++++++------ .../Media/LinearGradientBrushImpl.cs | 7 +-- .../Media/RadialGradientBrushImpl.cs | 11 ++-- .../Media/ConicGradientBrushTests.cs | 40 +++++++++++++++ .../Media/LinearGradientBrushTests.cs | 36 ++++++++++++++ .../Media/RadialGradientBrushTests.cs | 34 +++++++++++++ ...cGradientBrush_DrawingContext.expected.png | Bin 0 -> 631 bytes ...rGradientBrush_DrawingContext.expected.png | Bin 0 -> 2997 bytes ...lGradientBrush_DrawingContext.expected.png | Bin 0 -> 9127 bytes ...cGradientBrush_DrawingContext.expected.png | Bin 0 -> 10037 bytes ...rGradientBrush_DrawingContext.expected.png | Bin 0 -> 1908 bytes ...lGradientBrush_DrawingContext.expected.png | Bin 0 -> 5904 bytes 15 files changed, 163 insertions(+), 52 deletions(-) create mode 100644 tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png create mode 100644 tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png create mode 100644 tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png create mode 100644 tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2c5c2acd52..d8bd0607d1 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -164,7 +164,7 @@ namespace Avalonia.Skia /// public void DrawLine(IPen pen, Point p1, Point p2) { - using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) + using (var paint = CreatePaint(_strokePaint, pen, new Rect(p1, p2).Normalize())) { if (paint.Paint is object) { @@ -177,10 +177,10 @@ namespace Avalonia.Skia public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { var impl = (GeometryImpl) geometry; - var size = geometry.Bounds.Size; + var rect = geometry.Bounds; - using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper)) - using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper)) + using (var fill = brush != null ? CreatePaint(_fillPaint, brush, rect) : default(PaintWrapper)) + using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, rect) : default(PaintWrapper)) { if (fill.Paint != null) { @@ -354,7 +354,7 @@ namespace Avalonia.Skia if (brush != null) { - using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size)) + using (var paint = CreatePaint(_fillPaint, brush, rect.Rect)) { if (isRounded) { @@ -397,7 +397,7 @@ namespace Avalonia.Skia if (pen?.Brush != null) { - using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size)) + using (var paint = CreatePaint(_strokePaint, pen, rect.Rect)) { if (paint.Paint is object) { @@ -417,7 +417,7 @@ namespace Avalonia.Skia /// public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) { - using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds.Size)) + using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds)) { var textImpl = (FormattedTextImpl) text; textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering); @@ -427,7 +427,7 @@ namespace Avalonia.Skia /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { - using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size)) + using (var paintWrapper = CreatePaint(_fillPaint, foreground, new Rect(glyphRun.Size))) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; @@ -537,7 +537,7 @@ namespace Avalonia.Skia var paint = new SKPaint(); Canvas.SaveLayer(paint); - _maskStack.Push(CreatePaint(paint, mask, bounds.Size, true)); + _maskStack.Push(CreatePaint(paint, mask, bounds, true)); } /// @@ -593,18 +593,19 @@ namespace Avalonia.Skia /// Paint wrapper. /// Target size. /// Gradient brush. - private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush) + private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush) { var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode(); var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray(); var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray(); + var position = targetRect.Position.ToSKPoint(); switch (gradientBrush) { case ILinearGradientBrush linearGradient: { - var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint(); - var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint(); + var start = position + linearGradient.StartPoint.ToPixels(targetRect.Size).ToSKPoint(); + var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint(); // would be nice to cache these shaders possibly? using (var shader = @@ -617,10 +618,10 @@ namespace Avalonia.Skia } case IRadialGradientBrush radialGradient: { - var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint(); - var radius = (float)(radialGradient.Radius * targetSize.Width); + var center = position + radialGradient.Center.ToPixels(targetRect.Size).ToSKPoint(); + var radius = (float)(radialGradient.Radius * targetRect.Width); - var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint(); + var origin = position + radialGradient.GradientOrigin.ToPixels(targetRect.Size).ToSKPoint(); if (origin.Equals(center)) { @@ -665,7 +666,7 @@ namespace Avalonia.Skia } case IConicGradientBrush conicGradient: { - var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint(); + var center = position + conicGradient.Center.ToPixels(targetRect.Size).ToSKPoint(); // Skia's default is that angle 0 is from the right hand side of the center point // but we are matching CSS where the vertical point above the center is 0. @@ -867,10 +868,10 @@ namespace Avalonia.Skia /// /// The paint to wrap. /// Source brush. - /// Target size. + /// Target rect. /// Optional dispose of the supplied paint. /// Paint wrapper for given brush. - internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false) + internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Rect targetRect, bool disposePaint = false) { var paintWrapper = new PaintWrapper(paint, disposePaint); @@ -889,7 +890,7 @@ namespace Avalonia.Skia if (brush is IGradientBrush gradient) { - ConfigureGradientBrush(ref paintWrapper, targetSize, gradient); + ConfigureGradientBrush(ref paintWrapper, targetRect, gradient); return paintWrapper; } @@ -909,7 +910,7 @@ namespace Avalonia.Skia if (tileBrush != null && tileBrushImage != null) { - ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage); + ConfigureTileBrush(ref paintWrapper, targetRect.Size, tileBrush, tileBrushImage); } else { @@ -924,10 +925,10 @@ namespace Avalonia.Skia /// /// The paint to wrap. /// Source pen. - /// Target size. + /// Target rect. /// Optional dispose of the supplied paint. /// - private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false) + private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Rect targetRect, bool disposePaint = false) { // In Skia 0 thickness means - use hairline rendering // and for us it means - there is nothing rendered. @@ -936,7 +937,7 @@ namespace Avalonia.Skia return default; } - var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint); + var rv = CreatePaint(paint, pen.Brush, targetRect, disposePaint); paint.IsStroke = true; paint.StrokeWidth = (float) pen.Thickness; diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 5f4980e461..3eca42faa9 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -278,9 +278,9 @@ namespace Avalonia.Skia if (fb != null) { - //TODO: figure out how to get the brush size + //TODO: figure out how to get the brush rect currentWrapper = context.CreatePaint(new SKPaint { IsAntialias = true }, fb, - new Size()); + default); } else { diff --git a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs index 22c998df93..e4b2405290 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs @@ -34,10 +34,10 @@ namespace Avalonia.Direct2D1.Media { var wrapper = clientDrawingEffect as BrushWrapper; - // TODO: Work out how to get the size below rather than passing new Size(). + // TODO: Work out how to get the rect below rather than passing default. var brush = (wrapper == null) ? _foreground : - _context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush; + _context.CreateBrush(wrapper.Brush, default).PlatformBrush; _renderTarget.DrawGlyphRun( new RawVector2 { X = baselineOriginX, Y = baselineOriginY }, diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index af35934785..9336c9a7bb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -192,7 +192,7 @@ namespace Avalonia.Direct2D1.Media { using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value)) - using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) + using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect)) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D())) { if (d2dOpacityMask.PlatformBrush != null) @@ -217,9 +217,7 @@ namespace Avalonia.Direct2D1.Media { if (pen != null) { - var size = new Rect(p1, p2).Size; - - using (var d2dBrush = CreateBrush(pen.Brush, size)) + using (var d2dBrush = CreateBrush(pen.Brush, new Rect(p1, p2).Normalize())) using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) @@ -245,7 +243,7 @@ namespace Avalonia.Direct2D1.Media { if (brush != null) { - using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size)) + using (var d2dBrush = CreateBrush(brush, geometry.Bounds)) { if (d2dBrush.PlatformBrush != null) { @@ -257,7 +255,7 @@ namespace Avalonia.Direct2D1.Media if (pen != null) { - using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size)) + using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen))) using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) @@ -282,7 +280,7 @@ namespace Avalonia.Direct2D1.Media if (brush != null) { - using (var b = CreateBrush(brush, rect.Size)) + using (var b = CreateBrush(brush, rect)) { if (b.PlatformBrush != null) { @@ -311,7 +309,7 @@ namespace Avalonia.Direct2D1.Media if (pen?.Brush != null) { - using (var wrapper = CreateBrush(pen.Brush, rect.Size)) + using (var wrapper = CreateBrush(pen.Brush, rect)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (wrapper.PlatformBrush != null) @@ -349,7 +347,7 @@ namespace Avalonia.Direct2D1.Media { var impl = (FormattedTextImpl)text; - using (var brush = CreateBrush(foreground, impl.Bounds.Size)) + using (var brush = CreateBrush(foreground, impl.Bounds)) using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush)) { if (brush.PlatformBrush != null) @@ -367,7 +365,7 @@ namespace Avalonia.Direct2D1.Media /// The glyph run. public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { - using (var brush = CreateBrush(foreground, glyphRun.Size)) + using (var brush = CreateBrush(foreground, new Rect(glyphRun.Size))) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; @@ -458,9 +456,9 @@ namespace Avalonia.Direct2D1.Media /// Creates a Direct2D brush wrapper for a Avalonia brush. /// /// The avalonia brush. - /// The size of the brush's target area. + /// The brush's target area. /// The Direct2D brush wrapper. - public BrushImpl CreateBrush(IBrush brush, Size destinationSize) + public BrushImpl CreateBrush(IBrush brush, Rect destinationRect) { var solidColorBrush = brush as ISolidColorBrush; var linearGradientBrush = brush as ILinearGradientBrush; @@ -475,11 +473,11 @@ namespace Avalonia.Direct2D1.Media } else if (linearGradientBrush != null) { - return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize); + return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationRect); } else if (radialGradientBrush != null) { - return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize); + return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationRect); } else if (conicGradientBrush != null) { @@ -492,7 +490,7 @@ namespace Avalonia.Direct2D1.Media imageBrush, _deviceContext, (BitmapImpl)imageBrush.Source.PlatformImpl.Item, - destinationSize); + destinationRect.Size); } else if (visualBrush != null) { @@ -523,7 +521,7 @@ namespace Avalonia.Direct2D1.Media visualBrush, _deviceContext, new D2DBitmapImpl(intermediate.Bitmap), - destinationSize); + destinationRect.Size); } } } @@ -574,7 +572,7 @@ namespace Avalonia.Direct2D1.Media ContentBounds = PrimitiveExtensions.RectangleInfinite, MaskTransform = PrimitiveExtensions.Matrix3x2Identity, Opacity = 1, - OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush + OpacityBrush = CreateBrush(mask, bounds).PlatformBrush }; var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); _deviceContext.PushLayer(ref parameters, layer); diff --git a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs index 0e63d4cc03..69b45455ac 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media public LinearGradientBrushImpl( ILinearGradientBrush brush, SharpDX.Direct2D1.RenderTarget target, - Size destinationSize) + Rect destinationRect) { if (brush.GradientStops.Count == 0) { @@ -21,8 +21,9 @@ namespace Avalonia.Direct2D1.Media Position = (float)s.Offset }).ToArray(); - var startPoint = brush.StartPoint.ToPixels(destinationSize); - var endPoint = brush.EndPoint.ToPixels(destinationSize); + var position = destinationRect.Position; + var startPoint = position + brush.StartPoint.ToPixels(destinationRect.Size); + var endPoint = position + brush.EndPoint.ToPixels(destinationRect.Size); using (var stops = new SharpDX.Direct2D1.GradientStopCollection( target, diff --git a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs index 1fca6d4e33..7dcfd7e1e0 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media public RadialGradientBrushImpl( IRadialGradientBrush brush, SharpDX.Direct2D1.RenderTarget target, - Size destinationSize) + Rect destinationRect) { if (brush.GradientStops.Count == 0) { @@ -21,12 +21,13 @@ namespace Avalonia.Direct2D1.Media Position = (float)s.Offset }).ToArray(); - var centerPoint = brush.Center.ToPixels(destinationSize); - var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint; + var position = destinationRect.Position; + var centerPoint = position + brush.Center.ToPixels(destinationRect.Size); + var gradientOrigin = position + brush.GradientOrigin.ToPixels(destinationRect.Size) - centerPoint; // Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property - var radiusX = brush.Radius * destinationSize.Width; - var radiusY = brush.Radius * destinationSize.Height; + var radiusX = brush.Radius * destinationRect.Width; + var radiusY = brush.Radius * destinationRect.Height; using (var stops = new SharpDX.Direct2D1.GradientStopCollection( target, diff --git a/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs index db69a0a028..3d2f09e2a8 100644 --- a/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Media; +using System; using System.Threading.Tasks; using Xunit; @@ -174,5 +175,44 @@ namespace Avalonia.Direct2D1.RenderTests.Media await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task ConicGradientBrush_DrawingContext() + { + var brush = new ConicGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Yellow, Offset = 0.1667 }, + new GradientStop { Color = Colors.Lime, Offset = 0.3333 }, + new GradientStop { Color = Colors.Aqua, Offset = 0.5000 }, + new GradientStop { Color = Colors.Blue, Offset = 0.6667 }, + new GradientStop { Color = Colors.Magenta, Offset = 0.8333 }, + new GradientStop { Color = Colors.Red, Offset = 1 }, + } + }; + + Decorator target = new Decorator + { + Width = 200, + Height = 200, + Child = new DrawnControl(c => + { + c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); + c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100)); + }), + }; + + await RenderToFile(target); + CompareImages(); + } + + private class DrawnControl : Control + { + private readonly Action _render; + public DrawnControl(Action render) => _render = render; + public override void Render(DrawingContext context) => _render(context); + } } } diff --git a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs index af7b4f130f..28701f2f97 100644 --- a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs @@ -72,5 +72,41 @@ namespace Avalonia.Direct2D1.RenderTests.Media await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task LinearGradientBrush_DrawingContext() + { + var brush = new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), + EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + } + }; + + Decorator target = new Decorator + { + Width = 200, + Height = 200, + Child = new DrawnControl(c => + { + c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); + c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100)); + }), + }; + + await RenderToFile(target); + CompareImages(); + } + + private class DrawnControl : Control + { + private readonly Action _render; + public DrawnControl(Action render) => _render = render; + public override void Render(DrawingContext context) => _render(context); + } } } diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs index 2941b8dc34..95fae7f2fa 100644 --- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -165,5 +165,39 @@ namespace Avalonia.Direct2D1.RenderTests.Media await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task RadialGradientBrush_DrawingContext() + { + var brush = new RadialGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + } + }; + + Decorator target = new Decorator + { + Width = 200, + Height = 200, + Child = new DrawnControl(c => + { + c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); + c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100)); + }), + }; + + await RenderToFile(target); + CompareImages(); + } + + private class DrawnControl : Control + { + private readonly Action _render; + public DrawnControl(Action render) => _render = render; + public override void Render(DrawingContext context) => _render(context); + } } } diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3a2241b53f92cc878ba874d1af5fb67efd875848 GIT binary patch literal 631 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 HoGAW30rASt literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..85d11f2c2bb1cb02ee1f4b84770ef83fac040e93 GIT binary patch literal 9127 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3906d5a181a2fdb7ee70e1ce1f01820608d1a4de GIT binary patch literal 10037 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

GN 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 C-k?7K literal 0 HcmV?d00001 From b0972c86429d01a5d32ac207cf89208d269274bf Mon Sep 17 00:00:00 2001 From: Sattar Imamov Date: Tue, 15 Jun 2021 00:12:03 +0200 Subject: [PATCH 33/56] fixes RemoteWidget error when width or height is equal to zero. Issue #6068 --- src/Avalonia.Controls/Remote/RemoteWidget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index fabb38f87d..234960e87c 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -70,7 +70,7 @@ namespace Avalonia.Controls.Remote public override void Render(DrawingContext context) { - if (_lastFrame != null) + if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0) { var fmt = (PixelFormat) _lastFrame.Format; if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width || From c8b09d4156aa5d94aa7f490ec7155d3b1af460d5 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 15 Jun 2021 09:43:10 +0200 Subject: [PATCH 34/56] Update API compat. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index a79b3b4d7b..46c12ebd39 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -4,6 +4,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract. +MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. @@ -11,4 +12,4 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 12 +Total Issues: 13 From 6872b2a8a46cb47eb18cdc85cdff91e7e4394db2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 15 Jun 2021 11:53:40 +0200 Subject: [PATCH 35/56] Ensure we unsubscribe from PropertyChanged. --- src/Avalonia.Animation/AnimationInstance`1.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 0e1882ce75..b9ba49e510 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -46,9 +46,6 @@ namespace Avalonia.Animation _onCompleteAction = OnComplete; _interpolator = Interpolator; _baseClock = baseClock; - control.PropertyChanged += ControlPropertyChanged; - - UpdateNeutralValue(); FetchProperties(); } @@ -82,6 +79,7 @@ namespace Avalonia.Animation // Animation may have been stopped before it has finished. ApplyFinalFill(); + _targetControl.PropertyChanged -= ControlPropertyChanged; _timerSub?.Dispose(); _clock.PlayState = PlayState.Stop; } @@ -90,6 +88,8 @@ namespace Avalonia.Animation { _clock = new Clock(_baseClock); _timerSub = _clock.Subscribe(Step); + _targetControl.PropertyChanged += ControlPropertyChanged; + UpdateNeutralValue(); } public void Step(TimeSpan frameTick) From c7da3ccdc555d18910635102c485747d45bb2bc0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 15 Jun 2021 16:23:52 +0200 Subject: [PATCH 36/56] feat(DevTools): Startup Screen --- samples/ControlCatalog/MainWindow.xaml.cs | 5 ++++- .../Diagnostics/DevTools.cs | 4 +++- .../Diagnostics/DevToolsOptions.cs | 5 +++++ .../Diagnostics/ViewModels/MainViewModel.cs | 9 ++++++++- .../Diagnostics/Views/MainWindow.xaml.cs | 20 +++++++++++++++++++ 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 723351ae57..cd55ad78a7 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -17,7 +17,10 @@ namespace ControlCatalog public MainWindow() { this.InitializeComponent(); - this.AttachDevTools(); + this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() + { + StartupScreen = 1, + }); //Renderer.DrawFps = true; //Renderer.DrawDirtyRects = Renderer.DrawFps = true; diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 0e36c8f9cb..2a386f106e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -10,7 +10,8 @@ namespace Avalonia.Diagnostics { public static class DevTools { - private static readonly Dictionary s_open = new Dictionary(); + private static readonly Dictionary s_open = + new Dictionary(); public static IDisposable Attach(TopLevel root, KeyGesture gesture) { @@ -52,6 +53,7 @@ namespace Avalonia.Diagnostics Width = options.Size.Width, Height = options.Size.Height, }; + window.SetOptions(options); window.Closed += DevToolsClosed; s_open.Add(root, window); diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index f9978f3b7e..4677f1ef9b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -22,5 +22,10 @@ namespace Avalonia.Diagnostics /// Gets or sets the initial size of the DevTools window. The default value is 1280x720. /// public Size Size { get; set; } = new Size(1280, 720); + + ///

+ /// Get or set the startup screen number where the DevTools window will be displayed. + /// + public int? StartupScreen { get; set; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 72491bebc2..bf35641b40 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -151,7 +151,7 @@ namespace Avalonia.Diagnostics.ViewModels get { return _pointerOverElement; } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } } - + private void UpdateConsoleContext(ConsoleContext context) { context.root = _root; @@ -213,5 +213,12 @@ namespace Avalonia.Diagnostics.ViewModels tree.SelectControl(control); } } + + public int? StartupScreenIndex { get; private set; } = default; + + public void SetOptions(DevToolsOptions options) + { + StartupScreenIndex = options.StartupScreen; + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index bbb8e76551..9639386ddf 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -24,6 +24,23 @@ namespace Avalonia.Diagnostics.Views _keySubscription = InputManager.Instance.Process .OfType() .Subscribe(RawKeyDown); + + EventHandler? lh = default; + lh = (s, e) => + { + this.Opened -= lh; + if ((DataContext as MainViewModel)?.StartupScreenIndex is int index) + { + var screens = this.Screens; + if (index < screens.ScreenCount) + { + var screen = screens.All[index]; + this.Position = screen.Bounds.TopLeft; + this.WindowState = WindowState.Maximized; + } + } + }; + this.Opened += lh; } public TopLevel? Root @@ -115,5 +132,8 @@ namespace Avalonia.Diagnostics.Views } private void RootClosed(object? sender, EventArgs e) => Close(); + + public void SetOptions(DevToolsOptions options) => + (DataContext as MainViewModel)?.SetOptions(options); } } From fa133444c4104379c2de4229200355f2cd018e83 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 15 Jun 2021 18:16:03 +0200 Subject: [PATCH 37/56] fixes: Renamed StartupScreen to StartupScreenIndex --- samples/ControlCatalog/MainWindow.xaml.cs | 2 +- src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs | 4 ++-- .../Diagnostics/ViewModels/MainViewModel.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index cd55ad78a7..2446c0e1c9 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -19,7 +19,7 @@ namespace ControlCatalog this.InitializeComponent(); this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() { - StartupScreen = 1, + StartupScreenIndex = 1, }); //Renderer.DrawFps = true; //Renderer.DrawDirtyRects = Renderer.DrawFps = true; diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 4677f1ef9b..5336dca65b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -24,8 +24,8 @@ namespace Avalonia.Diagnostics public Size Size { get; set; } = new Size(1280, 720); /// - /// Get or set the startup screen number where the DevTools window will be displayed. + /// Get or set the startup screen index where the DevTools window will be displayed. /// - public int? StartupScreen { get; set; } + public int? StartupScreenIndex { get; set; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index bf35641b40..3f367165ac 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -218,7 +218,7 @@ namespace Avalonia.Diagnostics.ViewModels public void SetOptions(DevToolsOptions options) { - StartupScreenIndex = options.StartupScreen; + StartupScreenIndex = options.StartupScreenIndex; } } } From 02fd0006a9ddd11f88e3e7a821d779aa46c35e0d Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 15 Jun 2021 18:19:55 +0200 Subject: [PATCH 38/56] fixes: check StartupScreenIndex is greater than -1 --- src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 9639386ddf..d1232b749a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.Views if ((DataContext as MainViewModel)?.StartupScreenIndex is int index) { var screens = this.Screens; - if (index < screens.ScreenCount) + if (index > -1 && index < screens.ScreenCount) { var screen = screens.All[index]; this.Position = screen.Bounds.TopLeft; From 8f203c6800c3d5cc2e5b9332c7376574e1fa4747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 15 Jun 2021 21:57:25 +0200 Subject: [PATCH 39/56] Add Animator property to Setter --- src/Avalonia.Animation/Animation.cs | 2 +- src/Avalonia.Animation/IAnimationSetter.cs | 3 +++ src/Avalonia.Styling/Styling/Setter.cs | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index c42153ec4f..07b8c1a54e 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -248,7 +248,7 @@ namespace Avalonia.Animation { foreach (var setter in keyframe.Setters) { - var handler = GetAnimatorType(setter.Property); + var handler = setter.Animator ?? GetAnimatorType(setter.Property); if (handler == null) { diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs index 2d22377286..072d7096ae 100644 --- a/src/Avalonia.Animation/IAnimationSetter.cs +++ b/src/Avalonia.Animation/IAnimationSetter.cs @@ -1,8 +1,11 @@ +using System; + namespace Avalonia.Animation { public interface IAnimationSetter { AvaloniaProperty Property { get; set; } object Value { get; set; } + Type Animator { get; set; } } } diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index 1f3d6335a9..114a97dbb6 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/src/Avalonia.Styling/Styling/Setter.cs @@ -59,6 +59,11 @@ namespace Avalonia.Styling } } + /// + /// Gets or sets the property animator. + /// + public Type? Animator { get; set; } + public ISetterInstance Instance(IStyleable target) { target = target ?? throw new ArgumentNullException(nameof(target)); From d323d069df0747a60cd5f13cbf67899ab3fbd4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 15 Jun 2021 22:07:45 +0200 Subject: [PATCH 40/56] Create ApiCompatBaseline.txt --- src/Avalonia.Animation/ApiCompatBaseline.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/Avalonia.Animation/ApiCompatBaseline.txt diff --git a/src/Avalonia.Animation/ApiCompatBaseline.txt b/src/Avalonia.Animation/ApiCompatBaseline.txt new file mode 100644 index 0000000000..69b9dc5c77 --- /dev/null +++ b/src/Avalonia.Animation/ApiCompatBaseline.txt @@ -0,0 +1,5 @@ +Compat issues with assembly Avalonia.Animation: +InterfacesShouldHaveSameMembers : Interface member 'public System.Type Avalonia.Animation.IAnimationSetter.Animator' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Type Avalonia.Animation.IAnimationSetter.Animator.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Animation.IAnimationSetter.Animator.set(System.Type)' is present in the implementation but not in the contract. +Total Issues: 3 From 47482445643b70547ad55864d8289312a730f8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 16 Jun 2021 13:21:32 +0200 Subject: [PATCH 41/56] Add custom animator example --- samples/RenderDemo/MainWindow.xaml | 3 +++ .../RenderDemo/Pages/CustomAnimatorPage.xaml | 25 +++++++++++++++++ .../Pages/CustomAnimatorPage.xaml.cs | 27 +++++++++++++++++++ .../RenderDemo/Pages/CustomStringAnimator.cs | 16 +++++++++++ 4 files changed, 71 insertions(+) create mode 100644 samples/RenderDemo/Pages/CustomAnimatorPage.xaml create mode 100644 samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs create mode 100644 samples/RenderDemo/Pages/CustomStringAnimator.cs diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index aa165d13f7..f37df56b73 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -36,6 +36,9 @@ + + + diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml new file mode 100644 index 0000000000..6abb9a1217 --- /dev/null +++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs new file mode 100644 index 0000000000..eed8ee29ce --- /dev/null +++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs @@ -0,0 +1,27 @@ +using System.Reactive.Linq; +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using RenderDemo.ViewModels; + +namespace RenderDemo.Pages +{ + public class CustomAnimatorPage : UserControl + { + public CustomAnimatorPage() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/RenderDemo/Pages/CustomStringAnimator.cs b/samples/RenderDemo/Pages/CustomStringAnimator.cs new file mode 100644 index 0000000000..851a2d0187 --- /dev/null +++ b/samples/RenderDemo/Pages/CustomStringAnimator.cs @@ -0,0 +1,16 @@ +using Avalonia.Animation.Animators; + +namespace RenderDemo.Pages +{ + public class CustomStringAnimator : Animator + { + public override string Interpolate(double progress, string oldValue, string newValue) + { + if (newValue.Length == 0) return ""; + var step = 1.0 / newValue.Length; + var length = (int)(progress / step); + var result = newValue.Substring(0, length + 1); + return result; + } + } +} From 84dd710fc184c322a59f4f8a650d5573f2ba18de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 16 Jun 2021 13:53:35 +0200 Subject: [PATCH 42/56] Use attached property --- .../RenderDemo/Pages/CustomAnimatorPage.xaml | 4 ++-- src/Avalonia.Animation/Animation.cs | 18 +++++++++++++++++- src/Avalonia.Animation/IAnimationSetter.cs | 1 - src/Avalonia.Styling/Styling/Setter.cs | 5 ----- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml index 6abb9a1217..b386636cae 100644 --- a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml +++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml @@ -11,10 +11,10 @@ - + - + diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 07b8c1a54e..76fbfafaa5 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -194,6 +194,22 @@ namespace Avalonia.Animation [Content] public KeyFrames Children { get; } = new KeyFrames(); + private static readonly Dictionary s_animators = new Dictionary(); + + public static Type GetAnimator(object obj) + { + if (s_animators.TryGetValue(obj, out var type)) + { + return type; + } + return null; + } + + public static void SetAnimator(object obj, Type value) + { + s_animators[obj] = value; + } + private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ), @@ -248,7 +264,7 @@ namespace Avalonia.Animation { foreach (var setter in keyframe.Setters) { - var handler = setter.Animator ?? GetAnimatorType(setter.Property); + var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property); if (handler == null) { diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs index 072d7096ae..d916f19370 100644 --- a/src/Avalonia.Animation/IAnimationSetter.cs +++ b/src/Avalonia.Animation/IAnimationSetter.cs @@ -6,6 +6,5 @@ namespace Avalonia.Animation { AvaloniaProperty Property { get; set; } object Value { get; set; } - Type Animator { get; set; } } } diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index 114a97dbb6..1f3d6335a9 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/src/Avalonia.Styling/Styling/Setter.cs @@ -59,11 +59,6 @@ namespace Avalonia.Styling } } - /// - /// Gets or sets the property animator. - /// - public Type? Animator { get; set; } - public ISetterInstance Instance(IStyleable target) { target = target ?? throw new ArgumentNullException(nameof(target)); From 65b115c6e17321f45bf8b972b20ce005f5531284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 16 Jun 2021 14:03:33 +0200 Subject: [PATCH 43/56] Add comments --- src/Avalonia.Animation/Animation.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 76fbfafaa5..8571d49b42 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -194,8 +194,14 @@ namespace Avalonia.Animation [Content] public KeyFrames Children { get; } = new KeyFrames(); + // Store values for the Animator attached properties. private static readonly Dictionary s_animators = new Dictionary(); + /// + /// Gets the value of the Animator attached property for an object. + /// + /// The object. + /// The property animator type. public static Type GetAnimator(object obj) { if (s_animators.TryGetValue(obj, out var type)) @@ -205,6 +211,11 @@ namespace Avalonia.Animation return null; } + /// + /// Sets the value of the Animator attached property for an object. + /// + /// The object. + /// The property animator value. public static void SetAnimator(object obj, Type value) { s_animators[obj] = value; From f7cc89c86bba426b7449c3bab53d0976390ead94 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 16 Jun 2021 16:01:36 +0100 Subject: [PATCH 44/56] fix osx window state logic. --- native/Avalonia.Native/src/OSX/window.mm | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 870345e543..c0936356d2 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -513,6 +513,7 @@ private: bool _fullScreenActive; SystemDecorations _decorations; AvnWindowState _lastWindowState; + AvnWindowState _actualWindowState; bool _inSetWindowState; NSRect _preZoomSize; bool _transitioningWindowState; @@ -539,6 +540,7 @@ private: _transitioningWindowState = false; _inSetWindowState = false; _lastWindowState = Normal; + _actualWindowState = Normal; WindowEvents = events; [Window setCanBecomeKeyAndMain]; [Window disableCursorRects]; @@ -633,7 +635,7 @@ private: void WindowStateChanged () override { - if(!_inSetWindowState && !_transitioningWindowState) + if(_shown && !_inSetWindowState && !_transitioningWindowState) { AvnWindowState state; GetWindowState(&state); @@ -963,14 +965,14 @@ private: { @autoreleasepool { - if(_lastWindowState == state) + if(_actualWindowState == state) { return S_OK; } _inSetWindowState = true; - auto currentState = _lastWindowState; + auto currentState = _actualWindowState; _lastWindowState = state; if(currentState == Normal) @@ -1049,8 +1051,11 @@ private: } break; } + + _actualWindowState = _lastWindowState; } + _inSetWindowState = false; return S_OK; From 038805bb26c74f8896093fb0204c73082de34268 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 16 Jun 2021 17:23:48 +0200 Subject: [PATCH 45/56] Cache property changed delegate. --- src/Avalonia.Animation/AnimationInstance`1.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index b9ba49e510..cf79640150 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -37,6 +37,7 @@ namespace Avalonia.Animation private IDisposable _timerSub; private readonly IClock _baseClock; private IClock _clock; + private EventHandler _propertyChangedDelegate; public AnimationInstance(Animation animation, Animatable control, Animator animator, IClock baseClock, Action OnComplete, Func Interpolator) { @@ -79,7 +80,7 @@ namespace Avalonia.Animation // Animation may have been stopped before it has finished. ApplyFinalFill(); - _targetControl.PropertyChanged -= ControlPropertyChanged; + _targetControl.PropertyChanged -= _propertyChangedDelegate; _timerSub?.Dispose(); _clock.PlayState = PlayState.Stop; } @@ -88,7 +89,8 @@ namespace Avalonia.Animation { _clock = new Clock(_baseClock); _timerSub = _clock.Subscribe(Step); - _targetControl.PropertyChanged += ControlPropertyChanged; + _propertyChangedDelegate ??= ControlPropertyChanged; + _targetControl.PropertyChanged += _propertyChangedDelegate; UpdateNeutralValue(); } From 42a7c835fb24bba53508d220b88f361ea0213b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 17 Jun 2021 06:52:18 +0200 Subject: [PATCH 46/56] Delete ApiCompatBaseline.txt --- src/Avalonia.Animation/ApiCompatBaseline.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/Avalonia.Animation/ApiCompatBaseline.txt diff --git a/src/Avalonia.Animation/ApiCompatBaseline.txt b/src/Avalonia.Animation/ApiCompatBaseline.txt deleted file mode 100644 index 69b9dc5c77..0000000000 --- a/src/Avalonia.Animation/ApiCompatBaseline.txt +++ /dev/null @@ -1,5 +0,0 @@ -Compat issues with assembly Avalonia.Animation: -InterfacesShouldHaveSameMembers : Interface member 'public System.Type Avalonia.Animation.IAnimationSetter.Animator' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Type Avalonia.Animation.IAnimationSetter.Animator.get()' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Animation.IAnimationSetter.Animator.set(System.Type)' is present in the implementation but not in the contract. -Total Issues: 3 From 43bfff83a048ff78fe6989f3b874bf048605faba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 17 Jun 2021 06:52:45 +0200 Subject: [PATCH 47/56] Update IAnimationSetter.cs --- src/Avalonia.Animation/IAnimationSetter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs index d916f19370..2d22377286 100644 --- a/src/Avalonia.Animation/IAnimationSetter.cs +++ b/src/Avalonia.Animation/IAnimationSetter.cs @@ -1,5 +1,3 @@ -using System; - namespace Avalonia.Animation { public interface IAnimationSetter From 76d8bcff56a3e06bd3f74c433ea6da930208462e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 17 Jun 2021 07:08:56 +0200 Subject: [PATCH 48/56] Use IAnimationSetter instead of object to attached property --- src/Avalonia.Animation/Animation.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 8571d49b42..b315b65154 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -194,17 +194,17 @@ namespace Avalonia.Animation [Content] public KeyFrames Children { get; } = new KeyFrames(); - // Store values for the Animator attached properties. - private static readonly Dictionary s_animators = new Dictionary(); + // Store values for the Animator attached properties for IAnimationSetter objects. + private static readonly Dictionary s_animators = new Dictionary(); /// - /// Gets the value of the Animator attached property for an object. + /// Gets the value of the Animator attached property for a setter. /// - /// The object. + /// The animation setter. /// The property animator type. - public static Type GetAnimator(object obj) + public static Type GetAnimator(IAnimationSetter setter) { - if (s_animators.TryGetValue(obj, out var type)) + if (s_animators.TryGetValue(setter, out var type)) { return type; } @@ -212,11 +212,11 @@ namespace Avalonia.Animation } /// - /// Sets the value of the Animator attached property for an object. + /// Sets the value of the Animator attached property for a setter. /// - /// The object. + /// The animation setter. /// The property animator value. - public static void SetAnimator(object obj, Type value) + public static void SetAnimator(IAnimationSetter setter, Type value) { s_animators[obj] = value; } From ab28847bc6e0f7660a2455e735d0ba961696b2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 17 Jun 2021 08:07:41 +0200 Subject: [PATCH 49/56] Fix --- src/Avalonia.Animation/Animation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index b315b65154..daa4793ef0 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -218,7 +218,7 @@ namespace Avalonia.Animation /// The property animator value. public static void SetAnimator(IAnimationSetter setter, Type value) { - s_animators[obj] = value; + s_animators[setter] = value; } private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> From f10f54682161bdcfc87ea26d823924691bf90885 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Jun 2021 12:49:51 +0100 Subject: [PATCH 50/56] scale flyout transient area. --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 4725d17d65..ea47d15914 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -252,7 +252,9 @@ namespace Avalonia.Controls.Primitives if (Popup?.Host is PopupRoot root) { // Get the popup root bounds and convert to screen coordinates - var tmp = root.Bounds.Inflate(100); + var topLevel = root.Parent as TopLevel; + + var tmp = root.Bounds.Inflate(topLevel.PointToClient(new PixelPoint(100, 100)).X); var scPt = root.PointToScreen(tmp.TopLeft); enlargedPopupRect = new Rect(scPt.X, scPt.Y, tmp.Width, tmp.Height); } From b80ee8672954999a2c52212bff0deb5f458078eb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Jun 2021 12:53:06 +0100 Subject: [PATCH 51/56] no need for toplevel. --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index ea47d15914..b3b5b5238e 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -252,9 +252,7 @@ namespace Avalonia.Controls.Primitives if (Popup?.Host is PopupRoot root) { // Get the popup root bounds and convert to screen coordinates - var topLevel = root.Parent as TopLevel; - - var tmp = root.Bounds.Inflate(topLevel.PointToClient(new PixelPoint(100, 100)).X); + var tmp = root.Bounds.Inflate(root.PointToClient(new PixelPoint(100, 100)).X); var scPt = root.PointToScreen(tmp.TopLeft); enlargedPopupRect = new Rect(scPt.X, scPt.Y, tmp.Width, tmp.Height); } From 45299c15059732404ef07a88e0d2987af0186433 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Jun 2021 13:04:28 +0100 Subject: [PATCH 52/56] use renderscaling. --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index b3b5b5238e..73f2329513 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -4,6 +4,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; +using Avalonia.Rendering; #nullable enable @@ -252,7 +253,8 @@ namespace Avalonia.Controls.Primitives if (Popup?.Host is PopupRoot root) { // Get the popup root bounds and convert to screen coordinates - var tmp = root.Bounds.Inflate(root.PointToClient(new PixelPoint(100, 100)).X); + + var tmp = root.Bounds.Inflate(100 * (root as IRenderRoot).RenderScaling); var scPt = root.PointToScreen(tmp.TopLeft); enlargedPopupRect = new Rect(scPt.X, scPt.Y, tmp.Width, tmp.Height); } From b78d2a0e06808c32ef1573d7dda249fc4605f342 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Jun 2021 14:44:43 +0100 Subject: [PATCH 53/56] use Pixel api types when working in screen coordinates. --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 32 +++++++++------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 73f2329513..e4b68c62fd 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -52,8 +52,9 @@ namespace Avalonia.Controls.Primitives private bool _isOpen; private Control? _target; private FlyoutShowMode _showMode = FlyoutShowMode.Standard; - private Rect? enlargedPopupRect; - private IDisposable? transientDisposable; + private Rect? _enlargedPopupRect; + private PixelRect? _enlargePopupRectScreenPixelRect; + private IDisposable? _transientDisposable; protected Popup? Popup { get; private set; } @@ -164,8 +165,10 @@ namespace Avalonia.Controls.Primitives Popup.IsOpen = false; // Ensure this isn't active - transientDisposable?.Dispose(); - transientDisposable = null; + _transientDisposable?.Dispose(); + _transientDisposable = null; + _enlargedPopupRect = null; + _enlargePopupRectScreenPixelRect = null; OnClosed(); } @@ -231,7 +234,7 @@ namespace Avalonia.Controls.Primitives } else if (ShowMode == FlyoutShowMode.TransientWithDismissOnPointerMoveAway) { - transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss); + _transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss); } } @@ -247,21 +250,20 @@ namespace Avalonia.Controls.Primitives // For windowed popups, enlargedPopupRect is in screen coordinates, // for overlay popups, its in OverlayLayer coordinates - if (enlargedPopupRect == null) + if (_enlargedPopupRect == null && _enlargePopupRectScreenPixelRect == null) { // Only do this once when the Flyout opens & cache the result if (Popup?.Host is PopupRoot root) { // Get the popup root bounds and convert to screen coordinates - var tmp = root.Bounds.Inflate(100 * (root as IRenderRoot).RenderScaling); - var scPt = root.PointToScreen(tmp.TopLeft); - enlargedPopupRect = new Rect(scPt.X, scPt.Y, tmp.Width, tmp.Height); + var tmp = root.Bounds.Inflate(100); + _enlargePopupRectScreenPixelRect = new PixelRect(root.PointToScreen(tmp.TopLeft), root.PointToScreen(tmp.BottomRight)); } else if (Popup?.Host is OverlayPopupHost host) { // Overlay popups are in OverlayLayer coordinates, just use that - enlargedPopupRect = host.Bounds.Inflate(100); + _enlargedPopupRect = host.Bounds.Inflate(100); } return; @@ -275,24 +277,18 @@ namespace Avalonia.Controls.Primitives // window will not close this (as pointer events stop), which // does match UWP var pt = pArgs.Root.PointToScreen(pArgs.Position); - if (!enlargedPopupRect?.Contains(new Point(pt.X, pt.Y)) ?? false) + if (!_enlargePopupRectScreenPixelRect?.Contains(pt) ?? false) { HideCore(false); - enlargedPopupRect = null; - transientDisposable?.Dispose(); - transientDisposable = null; } } else if (Popup?.Host is OverlayPopupHost) { // Same as above here, but just different coordinate space // so we don't need to translate - if (!enlargedPopupRect?.Contains(pArgs.Position) ?? false) + if (!_enlargedPopupRect?.Contains(pArgs.Position) ?? false) { HideCore(false); - enlargedPopupRect = null; - transientDisposable?.Dispose(); - transientDisposable = null; } } } From b37ddadf811f97dd6ebaf181460b349d1c49e5f1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 17 Jun 2021 16:31:57 +0200 Subject: [PATCH 54/56] Implement measure pass in AdornerLayer. Previously the `AdornerLayer` was falling back to the base class implementation of `MeasureOverride`, in `Canvas`. This meant that at times, adorner controls were being measured with a larger constraint than the final size given to them. `Grid` doesn't like it when this happens which causes problems with various controls. Add a measure pass to `AdornerLayer` which passes the adorned control's bounds as the constraint (which is the value that the arrange pass will use). --- .../Primitives/AdornerLayer.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 9834bf3d3b..a397608aba 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using System.Linq; +using System.Resources; using Avalonia.Media; using Avalonia.Rendering; using Avalonia.Utilities; @@ -45,10 +46,27 @@ namespace Avalonia.Controls.Primitives ?.AdornerLayer; } - protected override Size ArrangeOverride(Size finalSize) + protected override Size MeasureOverride(Size availableSize) { - var parent = Parent; + foreach (var child in Children) + { + var info = child.GetValue(s_adornedElementInfoProperty); + + if (info != null && info.Bounds.HasValue) + { + child.Measure(info.Bounds.Value.Bounds.Size); + } + else + { + child.Measure(availableSize); + } + } + return default; + } + + protected override Size ArrangeOverride(Size finalSize) + { foreach (var child in Children) { var info = child.GetValue(s_adornedElementInfoProperty); From 7d863e877f52323cff846d9c0b835c30d54787f9 Mon Sep 17 00:00:00 2001 From: amwx Date: Fri, 18 Jun 2021 21:22:53 -0500 Subject: [PATCH 55/56] Let Data and Stretch be overridden --- src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index 2eee656dc6..60420f2f44 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -112,9 +112,7 @@ + VerticalAlignment="Center" /> + + From 1739ed4138be8a1572600dd7b809b05359489fae Mon Sep 17 00:00:00 2001 From: aguahombre Date: Mon, 21 Jun 2021 19:42:00 +0100 Subject: [PATCH 56/56] Adds a pixel format parameter to Linux frame buffer platform setup. (#6101) * Add pixel format parameter to Linux frame buffer platform setup. Currently setup always changes the frame buffer pixel format to RGBA which results in the screen being cleared if the default pixel format is not RGBA (as on a Raspberry PI) . This clears any splash screen and leaves the screen blank for a period which is not a good UX. Now the frame buffer setup can select the correct pixel format or use null to leave the pixel format unchanged. * Remove unnecessary formatting changes. Add v0.10.x compatible constructor. * Keep old StartLinuxFbDev extension method for v0.10.x binary compatibility --- .../LinuxFramebufferPlatform.cs | 5 +- .../LockedFramebuffer.cs | 2 +- .../Output/FbdevOutput.cs | 88 ++++++++++++++----- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index a6b70069c1..89f81a7649 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -132,7 +132,10 @@ public static class LinuxFramebufferPlatformExtensions { public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1) where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fbdev) {Scaling = scaling}); + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }); + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling) + where T : AppBuilderBase, new() => + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }); public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling}); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs b/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs index ed59166eb8..87c7b64c26 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs @@ -41,6 +41,6 @@ namespace Avalonia.LinuxFramebuffer public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres); public int RowBytes => (int) _fixedInfo.line_length; public Vector Dpi { get; } - public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; + public PixelFormat Format => _varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565 : _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs index b83fe6cbe8..add744ee16 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs @@ -16,16 +16,33 @@ namespace Avalonia.LinuxFramebuffer private IntPtr _mappedAddress; public double Scaling { get; set; } - public FbdevOutput(string fileName = null) + /// + /// Create a Linux frame buffer device output + /// + /// The frame buffer device name. + /// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set + public FbdevOutput(string fileName = null) : this(null, null) { - fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0"; + } + + /// + /// Create a Linux frame buffer device output + /// + /// The frame buffer device name. + /// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set + /// The required pixel format for the frame buffer. + /// A null value will leave the frame buffer in the current pixel format. + /// Otherwise sets the frame buffer to the required format + public FbdevOutput(string fileName, PixelFormat? format) + { + fileName ??= Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0"; _fd = NativeUnsafeMethods.open(fileName, 2, 0); if (_fd <= 0) throw new Exception("Error: " + Marshal.GetLastWin32Error()); try { - Init(); + Init(format); } catch { @@ -34,25 +51,28 @@ namespace Avalonia.LinuxFramebuffer } } - void Init() + void Init(PixelFormat? format) { fixed (void* pnfo = &_varInfo) { if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); - SetBpp(); + if (format.HasValue) + { + SetBpp(format.Value); - if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) - _varInfo.transp = new fb_bitfield(); + if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) + _varInfo.transp = new fb_bitfield(); - NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo); + NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo); - if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) - throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); + if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) + throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); - if (_varInfo.bits_per_pixel != 32) - throw new Exception("Unable to set 32-bit display mode"); + if (_varInfo.bits_per_pixel != 32) + throw new Exception("Unable to set 32-bit display mode"); + } } fixed(void*pnfo = &_fixedInfo) if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo)) @@ -70,17 +90,43 @@ namespace Avalonia.LinuxFramebuffer } } - void SetBpp() + void SetBpp(PixelFormat format) { - _varInfo.bits_per_pixel = 32; - _varInfo.grayscale = 0; - _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield + switch (format) { - length = 8 - }; - _varInfo.green.offset = 8; - _varInfo.blue.offset = 16; - _varInfo.transp.offset = 24; + case PixelFormat.Rgba8888: + _varInfo.bits_per_pixel = 32; + _varInfo.grayscale = 0; + _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield + { + length = 8 + }; + _varInfo.green.offset = 8; + _varInfo.blue.offset = 16; + _varInfo.transp.offset = 24; + break; + case PixelFormat.Bgra8888: + _varInfo.bits_per_pixel = 32; + _varInfo.grayscale = 0; + _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield + { + length = 8 + }; + _varInfo.green.offset = 8; + _varInfo.red.offset = 16; + _varInfo.transp.offset = 24; + break; + case PixelFormat.Rgb565: + _varInfo.bits_per_pixel = 16; + _varInfo.grayscale = 0; + _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield(); + _varInfo.red.length = 5; + _varInfo.green.offset = 5; + _varInfo.green.length = 6; + _varInfo.blue.offset = 11; + _varInfo.blue.length = 5; + break; + } } public string Id { get; private set; }