/// <summary> /// Transitions out of the state. Calls to <see cref="TransitionIn"/> and <see cref="TransitionOut"/> are synchronized /// and only one will be executed at a time, the rest will be blocked and executed in FIFO order. /// </summary> /// <paramref name="useTransitions">If true, use defined storyboard animations in the transition</paramref> public override async Task TransitionOut(VisualTransitionType type, bool useTransitions = true) { // If it's a transition to self and the transition out should be omitted return if (type == VisualTransitionType.ToTheSameSetup && !(await GetRepeatedTransition()).HasFlag(RepeatedTransitionBehavior.TransitionOut)) { return; } // Get into the semaphore await _TransitionSemaphore.WaitAsync(); // Reset the temporary setters DispatcherHelpers.RunAsync(() => TemporarySetters.ForEach((x) => x.Reset())); if (useTransitions) { bool sbDefined = false; // Get on the UI thread await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { // If the storyboard is defined if (TransitionOutStoryboard.WillComplete()) { sbDefined = true; // Run it TransitionOutStoryboard?.Begin(); } }); // If the UI thread task determined that storyboard was defined, start a task that will wait for the storyboard to finish if (sbDefined) { await Task.Run(() => _WaitForStoryboardToFinish.WaitOne()); } } // Transition finished; Release the semaphore _TransitionSemaphore.Release(); }
/// <summary> /// Transitions into the state. Cancels transition out operation (if it was happening). /// </summary> /// <param name="useTransitions"></param> /// <returns></returns> public override async Task TransitionIn(VisualTransitionType type, bool useTransitions = true) { // If there's already a transition in going on and we're not supposed to restart it, just return if (type == VisualTransitionType.ToTheSameSetup && !(await GetRepeatedTransition()).HasFlag(RepeatedTransitionBehavior.TransitionIn)) { return; } // If the CancellationTokenSource from the previous transition call is still alive cancel it _Cancellation?.Cancel(); // Create a new source for ourselves var cancellation = new CancellationTokenSource(); // And assign it to the class-wide variable _Cancellation = cancellation; if (useTransitions) { bool sbDefined = false; // Get on the UI thread await DispatcherHelpers.RunAsync(() => { // If the storyboard is defined if (TransitionInStoryboard.WillComplete()) { // Mark it for the rest of the Task sbDefined = true; // Pause the transition out storyboard TransitionOutStoryboard?.Pause(); // Run the transition in storyboard TransitionInStoryboard?.Begin(); } }); // If the UI thread task determined that storyboard was defined, // start a task that will wait for the storyboard to finish if (sbDefined) { // Wait for either for the storyboard to finish or for the CancellationTokenSource to be cancelled // and if the storyboard finished first apply the setters on UI thread //if (WaitHandle.WaitAny(new[] { cancellation.Token.WaitHandle, _WaitForStoryboard }) == 1) if (await Task.Run(() => WaitHandle.WaitAny(new[] { cancellation.Token.WaitHandle, _WaitForStoryboard }) == 1)) { // If we weren't cancelled remove the reference to the _Cancellation, otherwise the one cancelling // will provide their own CancellationTokenSource therefore removing the reference to ours _Cancellation = null; } } else if (!cancellation.IsCancellationRequested) { // If our cancellation was not removed by another call, remove the reference to it _Cancellation = null; } // Apply the setters DispatcherHelpers.RunAsync(() => { TemporarySetters?.ForEach((x) => x.Set()); Setters?.ForEach((x) => x.Set()); }); } cancellation.Dispose(); }