Skip to content

Commit

Permalink
fix(animation): anomalies when quickly transitioning between multiple…
Browse files Browse the repository at this point in the history
… VisualStates of same VSGroup
  • Loading branch information
Xiaoy312 committed Apr 26, 2023
1 parent d20bd4f commit 101606e
Show file tree
Hide file tree
Showing 7 changed files with 619 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Uno.UI.RuntimeTests.Helpers;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
using static Private.Infrastructure.TestServices;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation;

[TestClass]
[RunsOnUIThread]
public class Given_DoubleAnimationUsingKeyFrames
{
[TestMethod]
public async Task When_Quickly_Transitions()
{
// We are mimicking Fluent-style ToggleSwitch's ToggleStates here.
var sut = new TestPages.QuickMultiTransitionsPage();
WindowHelper.WindowContent = sut;
await WindowHelper.WaitForLoaded(sut);
await Task.Delay(1000);

// Quickly play through multiple visual-states to simulate what happens when tapping(*1) on a ToggleSwitch.
/* *1: The full tapping experience is: [InitialState: Off]->Dragging->Off->On or [InitialState: A]->B->A->C
* where A=Off, B=Dragging, C=Off
* SwitchKnobOn.Opacity ToggleStates\AOff->COn 1 @[ControlFasterAnimationDuration] f=Linear
* SwitchKnobOn.Opacity ToggleStates\BDragging->AOff 0 @[ControlFasterAnimationDuration] f=Linear
* SwitchKnobOn.Opacity ToggleStates\BDragging->COn 1 @[ControlFasterAnimationDuration] f=Linear
* SwitchKnobOn.Opacity ToggleStates\COn->AOff 0 @[ControlFasterAnimationDuration] f=Linear
* SwitchKnobOn.Opacity ToggleStates\COn->BDragging 1 @0 f=Linear
* SwitchKnobOn.Opacity ToggleStates\COn 1 @[ControlFasterAnimationDuration] f=Linear
*/
VisualStateManager.GoToState(sut, TestPages.QuickMultiTransitionsPage.TestStateNames.PhaseB, useTransitions: true);
VisualStateManager.GoToState(sut, TestPages.QuickMultiTransitionsPage.TestStateNames.PhaseA, useTransitions: true);
VisualStateManager.GoToState(sut, TestPages.QuickMultiTransitionsPage.TestStateNames.PhaseC, useTransitions: true);
await WindowHelper.WaitForIdle();
await Task.Delay(1000);

// Given that it involves a race condition, we use a matrix of 4x4 to boost the failure rate.
// If everything went smoothly, the end result should be an opacity of 1 for all the borders.
var total = sut.RootGrid.Children.OfType<Border>().Count();
var passed = sut.RootGrid.Children.OfType<Border>()
.Count(x => x.Opacity == 1.0);
Assert.AreEqual(total, passed, $"Only {passed} of {total} Border.Opacity is at expected value of 1.0");
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation.TestPages;

public sealed partial class QuickMultiTransitionsPage : Page
{
public static class TestStateNames
{
public const string PhaseA = nameof(PhaseA);
public const string PhaseB = nameof(PhaseB);
public const string PhaseC = nameof(PhaseC);
}

public QuickMultiTransitionsPage()
{
this.InitializeComponent();
this.Loaded += (s, e) => VisualStateManager.GoToState(this, TestStateNames.PhaseA, useTransitions: true);
}
}
18 changes: 14 additions & 4 deletions src/Uno.UI/UI/Xaml/Media/Animation/ColorAnimationUsingKeyFrames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace Windows.UI.Xaml.Media.Animation
partial class ColorAnimationUsingKeyFrames : Timeline, ITimeline
{
private readonly Stopwatch _activeDuration = new Stopwatch();
private bool _wasBeginScheduled;
private bool _wasRequestedToStop;
private int _replayCount = 1;
private ColorOffset? _startingValue;
private ColorOffset _finalValue;
Expand Down Expand Up @@ -79,15 +81,16 @@ internal override TimeSpan GetCalculatedDuration()
return base.GetCalculatedDuration();
}

bool _wasBeginScheduled;
void ITimeline.Begin()
{
if (!_wasBeginScheduled)
{
// We dispatch the begin so that we can use bindings on ColorKeyFrame.Value from RelativeParent.
// This works because the template bindings are executed just after the constructor.
// WARNING: This does not allow us to bind ColorKeyFrame.Value with ViewModel properties.

_wasBeginScheduled = true;
_wasRequestedToStop = false;

#if !NET461
#if __ANDROID__
Expand All @@ -97,14 +100,17 @@ void ITimeline.Begin()
#endif
#endif
{
if (KeyFrames.Count < 1)
_wasBeginScheduled = false;
if (KeyFrames.Count < 1 || // nothing to do
_wasRequestedToStop // was requested to stop, between Begin() and dispatched here
)
{
return; // nothing to do
return;
}
PropertyInfo?.CloneShareableObjectsInPath();
_wasBeginScheduled = false;
_activeDuration.Restart();
_replayCount = 1;
Expand Down Expand Up @@ -207,15 +213,19 @@ void ITimeline.Deactivate()
_currentAnimator.Cancel();//Stop the animator if it is running
_startingValue = null;
}

State = TimelineState.Stopped;
_wasRequestedToStop = true;
}

void ITimeline.Stop()
{
_currentAnimator?.Cancel(); // stop could be called before the initialization
_startingValue = null;
ClearValue();

State = TimelineState.Stopped;
_wasRequestedToStop = true;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace Windows.UI.Xaml.Media.Animation
public partial class DoubleAnimationUsingKeyFrames : Timeline, ITimeline
{
private readonly Stopwatch _activeDuration = new Stopwatch();
private bool _wasBeginScheduled;
private bool _wasRequestedToStop;
private int _replayCount = 1;
private double? _startingValue;
private double _finalValue;
Expand Down Expand Up @@ -55,15 +57,17 @@ internal override TimeSpan GetCalculatedDuration()
return base.GetCalculatedDuration();
}

bool _wasBeginScheduled;
void ITimeline.Begin()
{
if (!_wasBeginScheduled)
{
// We dispatch the begin so that we can use bindings on DoubleKeyFrame.Value from RelativeParent.
// This works because the template bindings are executed just after the constructor.
// WARNING: This does not allow us to bind DoubleKeyFrame.Value with ViewModel properties.

_wasBeginScheduled = true;
_wasRequestedToStop = false;

#if !NET461
#if __ANDROID__
_ = Dispatcher.RunAnimation(() =>
Expand All @@ -72,11 +76,14 @@ void ITimeline.Begin()
#endif
#endif
{
if (KeyFrames.Count < 1)
_wasBeginScheduled = false;
if (KeyFrames.Count < 1 || // nothing to do
_wasRequestedToStop // was requested to stop, between Begin() and dispatched here
)
{
return; // nothing to do
return;
}
_wasBeginScheduled = false;
_activeDuration.Restart();
_replayCount = 1;
Expand Down Expand Up @@ -175,15 +182,19 @@ void ITimeline.Deactivate()
_currentAnimator.Cancel();//Stop the animator if it is running
_startingValue = null;
}

State = TimelineState.Stopped;
_wasRequestedToStop = true;
}

void ITimeline.Stop()
{
_currentAnimator?.Cancel(); // stop could be called before the initialization
_startingValue = null;
ClearValue();

State = TimelineState.Stopped;
_wasRequestedToStop = true;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ void ITimeline.Stop()
);
}

// We explicitly call the Stop of the _frameScheduler befire teh Reste dispose it,
// so the EndReason will stopped instead of Aborted
// We explicitly call the Stop of the _frameScheduler before the Reset dispose it,
// so the EndReason will be Stopped instead of Aborted.
_frameScheduler?.Stop();

Reset();
Expand Down
1 change: 1 addition & 0 deletions src/Uno.UI/UI/Xaml/Media/Animation/Storyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public void Begin()

private void Play()
{
_runningChildren = 0;
if (Children != null && Children.Count > 0)
{
for (int i = 0; i < Children.Count; i++)
Expand Down

0 comments on commit 101606e

Please sign in to comment.