Browse Source

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

Make Transitions a styled property
pull/3958/head
Jumar Macato 6 years ago
committed by GitHub
parent
commit
80d233c342
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 76
      src/Avalonia.Animation/Animatable.cs
  2. 2
      src/Avalonia.Animation/TransitionInstance.cs
  3. 123
      tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
  4. 4
      tests/Avalonia.Animation.UnitTests/TransitionsTests.cs
  5. 45
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  6. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

76
src/Avalonia.Animation/Animatable.cs

@ -22,14 +22,10 @@ namespace Avalonia.Animation
/// <summary>
/// Defines the <see cref="Transitions"/> property.
/// </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 Transitions? _transitions;
private Dictionary<ITransition, TransitionState>? _transitionState;
/// <summary>
@ -44,36 +40,10 @@ namespace Avalonia.Animation
/// <summary>
/// Gets or sets the property transitions for the control.
/// </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>
@ -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<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)
{

2
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;
}

123
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<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)
{
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<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;
}
}
}

4
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
{

45
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 = @"
<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);
}
}
}
}

8
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -34,9 +34,11 @@ namespace Avalonia.Markup.Xaml.UnitTests
var parsed = (Grid)AvaloniaXamlLoader.Parse(@"
<Grid xmlns='https://github.com/avaloniaui' >
<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>");
Assert.Equal(1, parsed.Transitions.Count);

Loading…
Cancel
Save