From 3304d646c007d3cbe72784bde5227ed76133d377 Mon Sep 17 00:00:00 2001 From: Fusion86 Date: Sun, 15 Nov 2020 18:49:23 +0100 Subject: [PATCH 01/30] 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/30] 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/30] 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 5365b2fc9564d72d01873a06f6ec79684ef0d7e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Jun 2021 16:04:06 +0200 Subject: [PATCH 04/30] 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 05/30] 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 9b337d10de8fa415df3924c17455d5621cb2a9e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 9 Jun 2021 10:12:42 +0200 Subject: [PATCH 06/30] 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 07/30] 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 08/30] 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 09/30] 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 10/30] 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 11/30] 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 79ea6a4e380b74ac688ff8a7e98bab66ee12b491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sat, 12 Jun 2021 22:42:59 +0200 Subject: [PATCH 12/30] 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 13/30] 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 14/30] 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 15/30] 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 16/30] 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 b0972c86429d01a5d32ac207cf89208d269274bf Mon Sep 17 00:00:00 2001 From: Sattar Imamov Date: Tue, 15 Jun 2021 00:12:03 +0200 Subject: [PATCH 17/30] 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 c7da3ccdc555d18910635102c485747d45bb2bc0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 15 Jun 2021 16:23:52 +0200 Subject: [PATCH 18/30] 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 19/30] 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 20/30] 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 21/30] 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 22/30] 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 23/30] 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 24/30] 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 25/30] 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 26/30] 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 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 27/30] 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 28/30] 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 29/30] 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 30/30] 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)>