Skip to content

Commit

Permalink
fix(animations): Ensure VisualState and VisualTransition are material…
Browse files Browse the repository at this point in the history
…ized in the right resource context
  • Loading branch information
dr1rrb committed Aug 18, 2021
1 parent c7fba6a commit 77b769d
Showing 1 changed file with 38 additions and 31 deletions.
69 changes: 38 additions & 31 deletions src/Uno.UI/UI/Xaml/VisualStateGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Uno.UI;
using Windows.Foundation.Collections;
using Microsoft.Extensions.Logging;
using static Windows.UI.Xaml.Media.Animation.Timeline.TimelineState;

#if XAMARIN_IOS
using UIKit;
Expand Down Expand Up @@ -185,22 +186,41 @@ internal void GoToState(
this.Log().DebugFormat("Go to state [{0}/{1}] on [{2}]", Name, state?.Name, element);
}

var current = _current;
var target = (state, transition: FindTransition(current.state?.Name, state?.Name));
var currentValues = _current;
var targetValues = (state, transition: FindTransition(currentValues.state?.Name, state?.Name));

// As accessing to VisualState and VisualTransition properties (Storyboard ans Setters) may trigger the materialization of the VisualState,
// we ensure that this materialization occurs only in the right resource scope.
// Note: the "current" should have already been materialized.
(Storyboard transition, Storyboard animation, SetterBaseCollection setters) current, target;
#if !HAS_EXPENSIVE_TRYFINALLY
try
#endif
{
ResourceResolver.PushNewScope(_xamlScope);

current = (currentValues.transition?.Storyboard, currentValues.state?.Storyboard, currentValues.state?.Setters);
target = (targetValues.transition?.Storyboard, targetValues.state?.Storyboard, targetValues.state?.Setters);
}
#if !HAS_EXPENSIVE_TRYFINALLY
finally
#endif
{
ResourceResolver.PopScope();
}

// Stops running animations (transition or state's storyboard)
// Note about animations (as of 2021-08-16 win 19043):
// Any "running animation", either from the current transition or the current state's storyboard,
// is being "paused" for properties that are going to be animated by the "next animation" (again transition or target state's storyboard),
// and rollbacked for properties that won't be animated anymore.
var runningAnimation = current.transition?.Storyboard is { } currentTransition
&& currentTransition.State != Timeline.TimelineState.Stopped
? currentTransition
: current.state?.Storyboard;
var nextAnimation = target.transition?.Storyboard ?? target.state?.Storyboard;
var runningAnimation = (current.transition?.State ?? Stopped) is Stopped
? current.animation
: current.transition;
var nextAnimation = target.transition ?? target.animation;
if (runningAnimation != null)
{
if(nextAnimation is null)
if (nextAnimation is null)
{
runningAnimation.Stop();
}
Expand All @@ -218,11 +238,11 @@ internal void GoToState(
// the value is rollbacked before the transition
// * if the target has a setter for a property that was not affected by the current
// the value is applied only at the end of the transition
if (current.state is {} currentState)
if (current.setters is { } currentSetters)
{
foreach (var setter in currentState.Setters.OfType<Setter>())
foreach (var setter in currentSetters.OfType<Setter>())
{
if (element != null && (target.state?.Setters.OfType<Setter>().Any(o => o.HasSameTarget(setter, DependencyPropertyValuePrecedences.Animations, element)) ?? false))
if (element != null && (target.setters?.OfType<Setter>().Any(o => o.HasSameTarget(setter, DependencyPropertyValuePrecedences.Animations, element)) ?? false))
{
// We clear the value of the current setter only if there isn't any setter in the target state
// which changes the same target property (for perf ... and UWP behavior support regarding transition animation).
Expand All @@ -239,7 +259,7 @@ internal void GoToState(
}
}

_current = target;
_current = targetValues;

// For backward compatibility, we may apply the setters before the end of the transition.
if (FeatureConfiguration.VisualState.ApplySettersBeforeTransition)
Expand All @@ -248,7 +268,7 @@ internal void GoToState(
}

// Finally effectively apply the target state!
if (useTransitions && target.transition?.Storyboard is { } transitionAnimation)
if (useTransitions && target.transition is { } transitionAnimation)
{
// Note: As of 2021-08-16 win 19043, if the transitionAnimation is Repeat=Forever, we actually never apply the state!

Expand All @@ -259,7 +279,7 @@ void OnTransitionCompleted(object s, object a)
{
transitionAnimation.Completed -= OnTransitionCompleted;

if (target.state?.Storyboard is { } stateAnimation)
if (target.animation is { } stateAnimation)
{
transitionAnimation.TurnOverAnimationsTo(stateAnimation);
}
Expand All @@ -281,7 +301,7 @@ void ApplyTargetState()
}

// Starts target state animation
if (target.state?.Storyboard is { } stateAnimation)
if (target.animation is { } stateAnimation)
{
stateAnimation.Completed += OnStateStoryboardCompleted;
stateAnimation.Begin();
Expand All @@ -300,27 +320,14 @@ void OnStateStoryboardCompleted(object s, object a)

void ApplyTargetStateSetters()
{
if (target.state is null || element is null)
if (target.setters is null || element is null)
{
return;
}

#if !HAS_EXPENSIVE_TRYFINALLY
try
#endif
{
ResourceResolver.PushNewScope(_xamlScope);

foreach (var setter in target.state.Setters.OfType<Setter>())
{
setter.ApplyValue(DependencyPropertyValuePrecedences.Animations, element);
}
}
#if !HAS_EXPENSIVE_TRYFINALLY
finally
#endif
foreach (var setter in target.setters.OfType<Setter>())
{
ResourceResolver.PopScope();
setter.ApplyValue(DependencyPropertyValuePrecedences.Animations, element);
}
}
}
Expand Down

0 comments on commit 77b769d

Please sign in to comment.