From 5365b2fc9564d72d01873a06f6ec79684ef0d7e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 16:04:06 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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);