浏览代码

Merge pull request #3895 from AvaloniaUI/fixes/3887-transitions-styled-property

Make Transitions a styled property
Jumar Macato 5 年之前
父节点
当前提交
80d233c342

+ 32 - 44
src/Avalonia.Animation/Animatable.cs

@@ -22,14 +22,10 @@ namespace Avalonia.Animation
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Transitions"/> property.
         /// Defines the <see cref="Transitions"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<Animatable, Transitions> TransitionsProperty =
-            AvaloniaProperty.RegisterDirect<Animatable, Transitions>(
-                nameof(Transitions),
-                o => o.Transitions,
-                (o, v) => o.Transitions = v);
+        public static readonly StyledProperty<Transitions?> TransitionsProperty =
+            AvaloniaProperty.Register<Animatable, Transitions?>(nameof(Transitions));
 
 
         private bool _transitionsEnabled = true;
         private bool _transitionsEnabled = true;
-        private Transitions? _transitions;
         private Dictionary<ITransition, TransitionState>? _transitionState;
         private Dictionary<ITransition, TransitionState>? _transitionState;
 
 
         /// <summary>
         /// <summary>
@@ -44,36 +40,10 @@ namespace Avalonia.Animation
         /// <summary>
         /// <summary>
         /// Gets or sets the property transitions for the control.
         /// Gets or sets the property transitions for the control.
         /// </summary>
         /// </summary>
-        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);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -89,9 +59,9 @@ namespace Avalonia.Animation
             {
             {
                 _transitionsEnabled = true;
                 _transitionsEnabled = true;
 
 
-                if (_transitions is object)
+                if (Transitions is object)
                 {
                 {
-                    AddTransitions(_transitions);
+                    AddTransitions(Transitions);
                 }
                 }
             }
             }
         }
         }
@@ -109,21 +79,39 @@ namespace Avalonia.Animation
             {
             {
                 _transitionsEnabled = false;
                 _transitionsEnabled = false;
 
 
-                if (_transitions is object)
+                if (Transitions is object)
                 {
                 {
-                    RemoveTransitions(_transitions);
+                    RemoveTransitions(Transitions);
                 }
                 }
             }
             }
         }
         }
 
 
         protected sealed override void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
         protected sealed override void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
         {
-            if (_transitionsEnabled &&
-                _transitions is object &&
-                _transitionState is object &&
-                change.Priority > BindingPriority.Animation)
+            if (change.Property == TransitionsProperty && change.IsEffectiveValueChange)
+            {
+                var oldTransitions = change.OldValue.GetValueOrDefault<Transitions>();
+                var newTransitions = change.NewValue.GetValueOrDefault<Transitions>();
+
+                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)
                     if (transition.Property == change.Property)
                     {
                     {

+ 2 - 0
src/Avalonia.Animation/TransitionInstance.cs

@@ -19,6 +19,8 @@ namespace Avalonia.Animation
 
 
         public TransitionInstance(IClock clock, TimeSpan Duration)
         public TransitionInstance(IClock clock, TimeSpan Duration)
         {
         {
+            clock = clock ?? throw new ArgumentNullException(nameof(clock));
+
             _duration = Duration;
             _duration = Duration;
             _baseClock = clock;
             _baseClock = clock;
         }
         }

+ 110 - 13
tests/Avalonia.Animation.UnitTests/AnimatableTests.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Data;
 using Avalonia.Data;
+using Avalonia.Layout;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
 using Moq;
 using Moq;
@@ -16,7 +17,7 @@ namespace Avalonia.Animation.UnitTests
             var target = CreateTarget();
             var target = CreateTarget();
             var control = new Control
             var control = new Control
             {
             {
-                Transitions = { target.Object },
+                Transitions = new Transitions { target.Object },
             };
             };
 
 
             control.Opacity = 0.5;
             control.Opacity = 0.5;
@@ -37,7 +38,7 @@ namespace Avalonia.Animation.UnitTests
                 var target = CreateTarget();
                 var target = CreateTarget();
                 var control = new Control
                 var control = new Control
                 {
                 {
-                    Transitions = { target.Object },
+                    Transitions = new Transitions { target.Object },
                 };
                 };
 
 
                 var root = new TestRoot
                 var root = new TestRoot
@@ -213,30 +214,126 @@ namespace Avalonia.Animation.UnitTests
             sub.Verify(x => x.Dispose());
             sub.Verify(x => x.Dispose());
         }
         }
 
 
-        private static Mock<ITransition> CreateTarget()
+        [Fact]
+        public void Animation_Is_Cancelled_When_New_Style_Activates()
         {
         {
-            var target = new Mock<ITransition>();
-            var sub = new Mock<IDisposable>();
+            using (UnitTestApplication.Start(TestServices.RealStyler))
+            {
+                var target = CreateTarget();
+                var control = CreateStyledControl(target.Object);
+                var sub = new Mock<IDisposable>();
 
 
-            target.Setup(x => x.Property).Returns(Visual.OpacityProperty);
-            target.Setup(x => x.Apply(
-                It.IsAny<Animatable>(),
-                It.IsAny<IClock>(),
-                It.IsAny<object>(),
-                It.IsAny<object>())).Returns(sub.Object);
+                target.Setup(x => x.Apply(
+                    control,
+                    It.IsAny<IClock>(),
+                    1.0,
+                    0.5)).Returns(sub.Object);
 
 
-            return target;
+                control.Opacity = 0.5;
+
+                target.Verify(x => x.Apply(
+                    control,
+                    It.IsAny<Clock>(),
+                    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<IDisposable>();
+
+                control.Classes.Add("foo");
+                control.Width = 100;
+
+                target.Verify(x => x.Apply(
+                    control,
+                    It.IsAny<Clock>(),
+                    double.NaN,
+                    100.0),
+                    Times.Once);
+            }
+        }
+
+        private static Mock<ITransition> CreateTarget()
+        {
+            return CreateTransition(Visual.OpacityProperty);
         }
         }
 
 
         private static Control CreateControl(ITransition transition)
         private static Control CreateControl(ITransition transition)
         {
         {
             var control = new Control
             var control = new Control
             {
             {
-                Transitions = { transition },
+                Transitions = new Transitions { transition },
             };
             };
 
 
             var root = new TestRoot(control);
             var root = new TestRoot(control);
             return 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<Control>())
+                    {
+                        Setters =
+                        {
+                            new Setter
+                            {
+                                Property = Control.TransitionsProperty,
+                                Value = new Transitions { transition1 },
+                            }
+                        }
+                    },
+                    new Style(x => x.OfType<Control>().Class("foo"))
+                    {
+                        Setters =
+                        {
+                            new Setter
+                            {
+                                Property = Control.TransitionsProperty,
+                                Value = new Transitions { transition2 },
+                            }
+                        }
+                    }
+                }
+            };
+
+            var root = new TestRoot(control);
+            return control;
+        }
+
+        private static Mock<ITransition> CreateTransition(AvaloniaProperty property)
+        {
+            var target = new Mock<ITransition>();
+            var sub = new Mock<IDisposable>();
+
+            target.Setup(x => x.Property).Returns(property);
+            target.Setup(x => x.Apply(
+                It.IsAny<Animatable>(),
+                It.IsAny<IClock>(),
+                It.IsAny<object>(),
+                It.IsAny<object>())).Returns(sub.Object);
+
+            return target;
+        }
     }
     }
 }
 }

+ 2 - 2
tests/Avalonia.Animation.UnitTests/TransitionsTests.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Animation.UnitTests
             {
             {
                 var border = new Border
                 var border = new Border
                 {
                 {
-                    Transitions =
+                    Transitions = new Transitions
                     {
                     {
                         new DoubleTransition
                         new DoubleTransition
                         {
                         {
@@ -44,7 +44,7 @@ namespace Avalonia.Animation.UnitTests
             {
             {
                 var border = new Border
                 var border = new Border
                 {
                 {
-                    Transitions =
+                    Transitions = new Transitions
                     {
                     {
                         new DoubleTransition
                         new DoubleTransition
                         {
                         {

+ 45 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@@ -337,5 +337,50 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(Brushes.Red, listBox.Background);
                 Assert.Equal(Brushes.Red, listBox.Background);
             }
             }
         }
         }
+
+        [Fact]
+        public void Transitions_Can_Be_Styled()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+             xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <Window.Styles>
+        <Style Selector='Border'>
+            <Setter Property='Transitions'>
+                <Transitions>
+                    <DoubleTransition Property='Width' Duration='0:0:1'/>
+                </Transitions>
+            </Setter>
+        </Style>
+        <Style Selector='Border.foo'>
+            <Setter Property='Transitions'>
+                <Transitions>
+                    <DoubleTransition Property='Height' Duration='0:0:1'/>
+                </Transitions>
+            </Setter>
+        </Style>
+    </Window.Styles>
+    <Border/>
+</Window>";
+                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);
+            }
+        }
     }
     }
 }
 }

+ 5 - 3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@@ -34,9 +34,11 @@ namespace Avalonia.Markup.Xaml.UnitTests
             var parsed = (Grid)AvaloniaXamlLoader.Parse(@"
             var parsed = (Grid)AvaloniaXamlLoader.Parse(@"
 <Grid xmlns='https://github.com/avaloniaui' >
 <Grid xmlns='https://github.com/avaloniaui' >
   <Grid.Transitions>
   <Grid.Transitions>
-    <DoubleTransition Property='Opacity'
-       Easing='CircularEaseIn'
-       Duration='0:0:0.5' />
+    <Transitions>
+      <DoubleTransition Property='Opacity'
+        Easing='CircularEaseIn'
+        Duration='0:0:0.5' />
+    </Transitions>
   </Grid.Transitions>
   </Grid.Transitions>
 </Grid>");
 </Grid>");
             Assert.Equal(1, parsed.Transitions.Count);
             Assert.Equal(1, parsed.Transitions.Count);