diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 7d6df716b8..9e9b84537b 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -22,14 +22,10 @@ namespace Avalonia.Animation /// /// Defines the property. /// - public static readonly DirectProperty TransitionsProperty = - AvaloniaProperty.RegisterDirect( - nameof(Transitions), - o => o.Transitions, - (o, v) => o.Transitions = v); + public static readonly StyledProperty TransitionsProperty = + AvaloniaProperty.Register(nameof(Transitions)); private bool _transitionsEnabled = true; - private Transitions? _transitions; private Dictionary? _transitionState; /// @@ -44,36 +40,10 @@ namespace Avalonia.Animation /// /// Gets or sets the property transitions for the control. /// - public Transitions Transitions + public Transitions? Transitions { - get - { - if (_transitions is null) - { - _transitions = new Transitions(); - _transitions.CollectionChanged += TransitionsCollectionChanged; - } - - return _transitions; - } - set - { - // TODO: This is a hack, Setter should not replace transitions, but should add/remove. - if (value is null) - { - return; - } - - if (_transitions is object) - { - RemoveTransitions(_transitions); - _transitions.CollectionChanged -= TransitionsCollectionChanged; - } - - SetAndRaise(TransitionsProperty, ref _transitions, value); - _transitions.CollectionChanged += TransitionsCollectionChanged; - AddTransitions(_transitions); - } + get => GetValue(TransitionsProperty); + set => SetValue(TransitionsProperty, value); } /// @@ -89,9 +59,9 @@ namespace Avalonia.Animation { _transitionsEnabled = true; - if (_transitions is object) + if (Transitions is object) { - AddTransitions(_transitions); + AddTransitions(Transitions); } } } @@ -109,21 +79,39 @@ namespace Avalonia.Animation { _transitionsEnabled = false; - if (_transitions is object) + if (Transitions is object) { - RemoveTransitions(_transitions); + RemoveTransitions(Transitions); } } } protected sealed override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change) { - if (_transitionsEnabled && - _transitions is object && - _transitionState is object && - change.Priority > BindingPriority.Animation) + if (change.Property == TransitionsProperty && change.IsEffectiveValueChange) + { + var oldTransitions = change.OldValue.GetValueOrDefault(); + var newTransitions = change.NewValue.GetValueOrDefault(); + + if (oldTransitions is object) + { + oldTransitions.CollectionChanged -= TransitionsCollectionChanged; + RemoveTransitions(oldTransitions); + } + + if (newTransitions is object) + { + newTransitions.CollectionChanged += TransitionsCollectionChanged; + AddTransitions(newTransitions); + } + } + else if (_transitionsEnabled && + Transitions is object && + _transitionState is object && + !change.Property.IsDirect && + change.Priority > BindingPriority.Animation) { - foreach (var transition in _transitions) + foreach (var transition in Transitions) { if (transition.Property == change.Property) { diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index efbbed51b5..ad2001d621 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -19,6 +19,8 @@ namespace Avalonia.Animation public TransitionInstance(IClock clock, TimeSpan Duration) { + clock = clock ?? throw new ArgumentNullException(nameof(clock)); + _duration = Duration; _baseClock = clock; } diff --git a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs index e1169650a9..b5c61883e7 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Layout; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -16,7 +17,7 @@ namespace Avalonia.Animation.UnitTests var target = CreateTarget(); var control = new Control { - Transitions = { target.Object }, + Transitions = new Transitions { target.Object }, }; control.Opacity = 0.5; @@ -37,7 +38,7 @@ namespace Avalonia.Animation.UnitTests var target = CreateTarget(); var control = new Control { - Transitions = { target.Object }, + Transitions = new Transitions { target.Object }, }; var root = new TestRoot @@ -213,30 +214,126 @@ namespace Avalonia.Animation.UnitTests sub.Verify(x => x.Dispose()); } - private static Mock CreateTarget() + [Fact] + public void Animation_Is_Cancelled_When_New_Style_Activates() { - var target = new Mock(); - var sub = new Mock(); + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + var target = CreateTarget(); + var control = CreateStyledControl(target.Object); + var sub = new Mock(); - target.Setup(x => x.Property).Returns(Visual.OpacityProperty); - target.Setup(x => x.Apply( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())).Returns(sub.Object); + target.Setup(x => x.Apply( + control, + It.IsAny(), + 1.0, + 0.5)).Returns(sub.Object); - return target; + control.Opacity = 0.5; + + target.Verify(x => x.Apply( + control, + It.IsAny(), + 1.0, + 0.5), + Times.Once); + + control.Classes.Add("foo"); + + sub.Verify(x => x.Dispose()); + } + } + + [Fact] + public void Transition_From_Style_Trigger_Is_Applied() + { + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + var target = CreateTransition(Control.WidthProperty); + var control = CreateStyledControl(transition2: target.Object); + var sub = new Mock(); + + control.Classes.Add("foo"); + control.Width = 100; + + target.Verify(x => x.Apply( + control, + It.IsAny(), + double.NaN, + 100.0), + Times.Once); + } + } + + private static Mock CreateTarget() + { + return CreateTransition(Visual.OpacityProperty); } private static Control CreateControl(ITransition transition) { var control = new Control { - Transitions = { transition }, + Transitions = new Transitions { transition }, }; var root = new TestRoot(control); return control; } + + private static Control CreateStyledControl( + ITransition transition1 = null, + ITransition transition2 = null) + { + transition1 = transition1 ?? CreateTarget().Object; + transition2 = transition2 ?? CreateTransition(Control.WidthProperty).Object; + + var control = new Control + { + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter + { + Property = Control.TransitionsProperty, + Value = new Transitions { transition1 }, + } + } + }, + new Style(x => x.OfType().Class("foo")) + { + Setters = + { + new Setter + { + Property = Control.TransitionsProperty, + Value = new Transitions { transition2 }, + } + } + } + } + }; + + var root = new TestRoot(control); + return control; + } + + private static Mock CreateTransition(AvaloniaProperty property) + { + var target = new Mock(); + var sub = new Mock(); + + target.Setup(x => x.Property).Returns(property); + target.Setup(x => x.Apply( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).Returns(sub.Object); + + return target; + } } } diff --git a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs index 22f3b4f501..70ffd781a1 100644 --- a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs +++ b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs @@ -16,7 +16,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = + Transitions = new Transitions { new DoubleTransition { @@ -44,7 +44,7 @@ namespace Avalonia.Animation.UnitTests { var border = new Border { - Transitions = + Transitions = new Transitions { new DoubleTransition { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 02f0d7072c..9642f5719d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -337,5 +337,50 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(Brushes.Red, listBox.Background); } } + + [Fact] + public void Transitions_Can_Be_Styled() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var border = (Border)window.Content; + + Assert.Equal(1, border.Transitions.Count); + Assert.Equal(Border.WidthProperty, border.Transitions[0].Property); + + border.Classes.Add("foo"); + + Assert.Equal(1, border.Transitions.Count); + Assert.Equal(Border.HeightProperty, border.Transitions[0].Property); + + border.Classes.Remove("foo"); + + Assert.Equal(1, border.Transitions.Count); + Assert.Equal(Border.WidthProperty, border.Transitions[0].Property); + } + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 4ff9e3db38..a408069cb0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -34,9 +34,11 @@ namespace Avalonia.Markup.Xaml.UnitTests var parsed = (Grid)AvaloniaXamlLoader.Parse(@" - + + + "); Assert.Equal(1, parsed.Transitions.Count);