diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs index c6aab5c4d5..204e918071 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs @@ -41,13 +41,13 @@ namespace ControlCatalog.Pages _carousel.PageTransition = null; break; case 1: - _carousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), _orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical); + _carousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), _orientation.SelectedIndex == 0 ? PageSlideAxis.Horizontal : PageSlideAxis.Vertical); break; case 2: _carousel.PageTransition = new CrossFade(TimeSpan.FromSeconds(0.25)); break; case 3: - _carousel.PageTransition = new Rotate3DTransition(TimeSpan.FromSeconds(0.5), _orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical); + _carousel.PageTransition = new Rotate3DTransition(TimeSpan.FromSeconds(0.5), _orientation.SelectedIndex == 0 ? PageSlideAxis.Horizontal : PageSlideAxis.Vertical); break; } } diff --git a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs index 03ffb44b4a..01fe5cf174 100644 --- a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs @@ -113,8 +113,8 @@ namespace ControlCatalog.ViewModels } PageTransitions[1].Transition = new CrossFade(TimeSpan.FromMilliseconds(Duration)); - PageTransitions[2].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Horizontal); - PageTransitions[3].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Vertical); + PageTransitions[2].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlideAxis.Horizontal); + PageTransitions[3].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlideAxis.Vertical); var compositeTransition = new CompositePageTransition(); compositeTransition.PageTransitions.Add(PageTransitions[1].Transition!); diff --git a/src/Avalonia.Base/Animation/CrossFade.cs b/src/Avalonia.Base/Animation/CrossFade.cs index 8c2fd1dc43..142faea27a 100644 --- a/src/Avalonia.Base/Animation/CrossFade.cs +++ b/src/Avalonia.Base/Animation/CrossFade.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using Avalonia.Animation.Easings; using Avalonia.Styling; @@ -10,10 +7,10 @@ namespace Avalonia.Animation /// /// Defines a cross-fade animation between two s. /// - public class CrossFade : IPageTransition + public class CrossFade : PageTransition { - private readonly Animation _fadeOutAnimation; - private readonly Animation _fadeInAnimation; + private readonly Animation _fadeOut; + private readonly Animation _fadeIn; /// /// Initializes a new instance of the class. @@ -28,27 +25,57 @@ namespace Avalonia.Animation /// /// The duration of the animation. public CrossFade(TimeSpan duration) + : base(duration) { - _fadeOutAnimation = new Animation + _fadeOut = CreateFadeAnimation(duration, 1, 0); + _fadeIn = CreateFadeAnimation(duration, 0, 1); + } + + /// + /// Gets or sets element entrance easing. + /// + public Easing FadeInEasing + { + get => _fadeIn.Easing; + set => _fadeIn.Easing = value; + } + + /// + /// Gets or sets element exit easing. + /// + public Easing FadeOutEasing + { + get => _fadeOut.Easing; + set => _fadeOut.Easing = value; + } + + protected override Animation GetShowAnimation(Visual? from, Visual? to, bool forward) => _fadeIn; + protected override Animation GetHideAnimation(Visual? from, Visual? to, bool forward) => _fadeOut; + + protected override void InvalidateCachedAnimations() + { + base.InvalidateCachedAnimations(); + _fadeIn.Duration = _fadeOut.Duration = Duration; + } + + private static Animation CreateFadeAnimation(TimeSpan duration, double fromOpacity, double toOpacity) + { + return new Animation { + Duration = duration, Children = { new KeyFrame() { Setters = { - new Setter - { - Property = Visual.IsVisibleProperty, - Value = true, - }, new Setter { Property = Visual.OpacityProperty, - Value = 1d + Value = fromOpacity, }, }, - Cue = new Cue(0) + Cue = new Cue(0), }, new KeyFrame() { @@ -57,132 +84,13 @@ namespace Avalonia.Animation new Setter { Property = Visual.OpacityProperty, - Value = 0d + Value = toOpacity, }, }, - Cue = new Cue(1) - }, - } - }; - _fadeInAnimation = new Animation - { - Children = - { - new KeyFrame() - { - Setters = - { - new Setter - { - Property = Visual.OpacityProperty, - Value = 0d - } - }, - Cue = new Cue(0) - }, - new KeyFrame() - { - Setters = - { - new Setter - { - Property = Visual.OpacityProperty, - Value = 1d - } - }, - Cue = new Cue(1) + Cue = new Cue(1), }, } }; - _fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration; - } - - /// - /// Gets the duration of the animation. - /// - public TimeSpan Duration - { - get => _fadeOutAnimation.Duration; - set => _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value; - } - - /// - /// Gets or sets element entrance easing. - /// - public Easing FadeInEasing - { - get => _fadeInAnimation.Easing; - set => _fadeInAnimation.Easing = value; - } - - /// - /// Gets or sets element exit easing. - /// - public Easing FadeOutEasing - { - get => _fadeOutAnimation.Easing; - set => _fadeOutAnimation.Easing = value; - } - - /// - public async Task Start(Visual? from, Visual? to, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return; - } - - var tasks = new List(); - var initialFromVisible = true; - var initialToVisible = true; - - if (from != null) - { - tasks.Add(_fadeOutAnimation.RunAsync(from, null, cancellationToken)); - - // Make "from" control invisible: this is overridden in the fade out animation, so - // will only take effect when the animation is completed. - initialFromVisible = from.IsVisible; - from.IsVisible = false; - } - - if (to != null) - { - initialToVisible = to.IsVisible; - to.IsVisible = true; - tasks.Add(_fadeInAnimation.RunAsync(to, null, cancellationToken)); - } - - await Task.WhenAll(tasks); - - if (cancellationToken.IsCancellationRequested) - { - if (from != null) - from.IsVisible = initialFromVisible; - if (to != null) - to.IsVisible = initialToVisible; - } - } - - /// - /// Starts the animation. - /// - /// - /// The control that is being transitioned away from. May be null. - /// - /// - /// The control that is being transitioned to. May be null. - /// - /// - /// Unused for cross-fades. - /// - /// allowed cancel transition - /// - /// A that tracks the progress of the animation. - /// - Task IPageTransition.Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) - { - return Start(from, to, cancellationToken); } } } diff --git a/src/Avalonia.Base/Animation/PageSlide.cs b/src/Avalonia.Base/Animation/PageSlide.cs index 2056dcfda7..a6e0f1d98f 100644 --- a/src/Avalonia.Base/Animation/PageSlide.cs +++ b/src/Avalonia.Base/Animation/PageSlide.cs @@ -1,32 +1,32 @@ using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using Avalonia.Animation.Easings; using Avalonia.Media; using Avalonia.Styling; -using Avalonia.VisualTree; namespace Avalonia.Animation { + /// + /// The axis on which a should occur + /// + public enum PageSlideAxis + { + Horizontal, + Vertical + } + /// /// Transitions between two pages by sliding them horizontally or vertically. /// - public class PageSlide : IPageTransition + public class PageSlide : PageTransition { - /// - /// The axis on which the PageSlide should occur - /// - public enum SlideAxis - { - Horizontal, - Vertical - } + private readonly Animation _slideOut; + private readonly Animation _slideIn; /// /// Initializes a new instance of the class. /// public PageSlide() + : this(TimeSpan.Zero) { } @@ -35,128 +35,78 @@ namespace Avalonia.Animation /// /// The duration of the animation. /// The axis on which the animation should occur - public PageSlide(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal) + public PageSlide(TimeSpan duration, PageSlideAxis orientation = PageSlideAxis.Horizontal) + : base(duration) { - Duration = duration; - Orientation = orientation; + var property = orientation == PageSlideAxis.Horizontal ? + TranslateTransform.XProperty : TranslateTransform.YProperty; + _slideIn = CreateSlideAnimation(duration, property); + _slideOut = CreateSlideAnimation(duration, property); } /// - /// Gets the duration of the animation. + /// Gets the direction of the animation. /// - public TimeSpan Duration { get; set; } + public PageSlideAxis Orientation + { + get + { + return _slideIn.Children[0].Setters[0].Property == TranslateTransform.XProperty ? + PageSlideAxis.Horizontal : PageSlideAxis.Vertical; + } + + set + { + if (Orientation != value) + { + var property = value == PageSlideAxis.Horizontal ? + TranslateTransform.XProperty : TranslateTransform.YProperty; + _slideIn.Children[0].Setters[0].Property = property; + _slideIn.Children[1].Setters[0].Property = property; + _slideOut.Children[0].Setters[0].Property = property; + _slideOut.Children[1].Setters[0].Property = property; + } + } + } - /// - /// Gets the duration of the animation. - /// - public SlideAxis Orientation { get; set; } - /// /// Gets or sets element entrance easing. /// - public Easing SlideInEasing { get; set; } = new LinearEasing(); + public Easing SlideInEasing + { + get => _slideIn.Easing; + set => _slideIn.Easing = value; + } /// /// Gets or sets element exit easing. /// - public Easing SlideOutEasing { get; set; } = new LinearEasing(); - - /// - public virtual async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) + public Easing SlideOutEasing { - if (cancellationToken.IsCancellationRequested) - { - return; - } + get => _slideOut.Easing; + set => _slideOut.Easing = value; + } - var tasks = new List(); + protected override Animation GetHideAnimation(Visual? from, Visual? to, bool forward) + { var parent = GetVisualParent(from, to); - var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; - var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty; - var initialFromVisible = true; - var initialToVisible = true; - - if (from != null) - { - var animation = new Animation - { - Easing = SlideOutEasing, - Children = - { - new KeyFrame - { - Setters = - { - new Setter { Property = Visual.IsVisibleProperty, Value = true }, - new Setter { Property = translateProperty, Value = 0d }, - }, - Cue = new Cue(0d) - }, - new KeyFrame - { - Setters = - { - new Setter - { - Property = translateProperty, - Value = forward ? -distance : distance - } - }, - Cue = new Cue(1d) - } - }, - Duration = Duration - }; - tasks.Add(animation.RunAsync(from, null, cancellationToken)); - - // Make "from" control invisible: this is overridden in the fade out animation, so - // will only take effect when the animation is completed. - initialFromVisible = from.IsVisible; - from.IsVisible = false; - } - - if (to != null) - { - var animation = new Animation - { - Easing = SlideInEasing, - Children = - { - new KeyFrame - { - Setters = - { - new Setter - { - Property = translateProperty, - Value = forward ? distance : -distance - } - }, - Cue = new Cue(0d) - }, - new KeyFrame - { - Setters = { new Setter { Property = translateProperty, Value = 0d } }, - Cue = new Cue(1d) - } - }, - Duration = Duration - }; - - initialToVisible = to.IsVisible; - to.IsVisible = true; - tasks.Add(animation.RunAsync(to, null, cancellationToken)); - } + var distance = Orientation == PageSlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; + _slideOut.Children[1].Setters[0].Value = forward ? -distance : distance; + return _slideOut; + } - await Task.WhenAll(tasks); + protected override Animation GetShowAnimation(Visual? from, Visual? to, bool forward) + { + var parent = GetVisualParent(from, to); + var distance = Orientation == PageSlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; + _slideIn.Children[0].Setters[0].Value = forward ? distance : -distance; + return _slideIn; + } - if (cancellationToken.IsCancellationRequested) - { - if (from != null) - from.IsVisible = initialFromVisible; - if (to != null) - to.IsVisible = initialToVisible; - } + protected override void InvalidateCachedAnimations() + { + base.InvalidateCachedAnimations(); + _slideIn.Duration = _slideOut.Duration = Duration; } /// @@ -171,7 +121,7 @@ namespace Avalonia.Animation /// /// Any one of the parameters may be null, but not both. /// - protected static Visual GetVisualParent(Visual? from, Visual? to) + internal static Visual GetVisualParent(Visual? from, Visual? to) { var p1 = (from ?? to)!.VisualParent; var p2 = (to ?? from)!.VisualParent; @@ -183,5 +133,40 @@ namespace Avalonia.Animation return p1 ?? throw new InvalidOperationException("Cannot determine visual parent."); } + + private static Animation CreateSlideAnimation(TimeSpan duration, AvaloniaProperty property) + { + return new Animation + { + Duration = duration, + Children = + { + new KeyFrame + { + Setters = + { + new Setter + { + Property = property, + Value = 0.0, + }, + }, + Cue = new Cue(0) + }, + new KeyFrame + { + Setters = + { + new Setter + { + Property = property, + Value = 0.0 + } + }, + Cue = new Cue(1) + } + }, + }; + } } } diff --git a/src/Avalonia.Base/Animation/PageTransition.cs b/src/Avalonia.Base/Animation/PageTransition.cs new file mode 100644 index 0000000000..d5eb8ad55d --- /dev/null +++ b/src/Avalonia.Base/Animation/PageTransition.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Styling; + +namespace Avalonia.Animation +{ + /// + /// Base class for animations that transition between two controls. + /// + public abstract class PageTransition : IPageTransition + { + private TimeSpan _duration; + private Animation? _hideVisibilityAnimation; + private Animation? _showVisibilityAnimation; + + /// + /// Initializes a new instance of the class. + /// + public PageTransition() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public PageTransition(TimeSpan duration) + { + _duration = duration; + } + + /// + /// Gets or sets the duration of the animation. + /// + public TimeSpan Duration + { + get => _duration; + set + { + if (_duration != value) + { + _duration = value; + InvalidateCachedAnimations(); + } + } + } + + /// + /// Starts the animation. + /// + /// + /// The control that is being transitioned away from. May be null. + /// + /// + /// The control that is being transitioned to. May be null. + /// + /// + /// Animation cancellation. + /// + /// + /// A that tracks the progress of the animation. + /// + /// + /// The and controls will be made visible + /// and transitioned to . At the end of the + /// animation (when the returned task completes), will be made + /// invisible but all other properties involved in the transition will have been left + /// unchanged. + /// + public Task Start(Visual? from, Visual? to, CancellationToken cancellationToken) + { + return Start(from, to, true, cancellationToken); + } + + /// + /// Starts the animation. + /// + /// + /// The control that is being transitioned away from. May be null. + /// + /// + /// The control that is being transitioned to. May be null. + /// + /// + /// If the animation is bidirectional, controls the direction of the animation. + /// + /// + /// Animation cancellation. + /// + /// + /// A that tracks the progress of the animation. + /// + /// + /// The and controls will be made visible + /// and transitioned to . At the end of the + /// animation (when the returned task completes), will be made + /// invisible but all other properties involved in the transition will have been left + /// unchanged. + /// + public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return; + + List? tasks = null; + + if (from is not null) + { + tasks ??= new(); + tasks.Add(GetHideVisibilityAnimation().RunAsync(from, null, cancellationToken)); + tasks.Add(GetHideAnimation(from, to, forward).RunAsync(from, null, cancellationToken)); + } + + if (to is not null) + { + tasks ??= new(); + tasks.Add(GetShowAnimation(from, to, forward).RunAsync(to, null, cancellationToken)); + tasks.Add(GetShowVisibilityAnimation().RunAsync(to, null, cancellationToken)); + } + + if (tasks is not null) + await Task.WhenAll(tasks); + } + + /// + /// When implemented in a derived class, returns the animation used to transition away from + /// the control. + /// + /// + /// The control that is being transitioned away from. May be null. + /// + /// + /// The control that is being transitioned to. May be null. + /// + /// + /// If the animation is bidirectional, controls the direction of the animation. + /// + protected abstract Animation GetHideAnimation(Visual? from, Visual? to, bool forward); + + /// + /// When implemented in a derived class, returns the animation used to transition to the + /// control. + /// + /// + /// The control that is being transitioned away from. May be null. + /// + /// + /// The control that is being transitioned to. May be null. + /// + /// + /// If the animation is bidirectional, controls the direction of the animation. + /// + protected abstract Animation GetShowAnimation(Visual? from, Visual? to, bool forward); + + /// + /// Called when a property that affects the animation is changed. + /// + protected virtual void InvalidateCachedAnimations() + { + if (_hideVisibilityAnimation is not null) + _hideVisibilityAnimation.Duration = _duration; + if (_showVisibilityAnimation is not null) + _showVisibilityAnimation.Duration = _duration; + } + + private Animation GetHideVisibilityAnimation() + { + return _hideVisibilityAnimation ??= CreateIsVisibleAnimation(Duration, false); + } + + private Animation GetShowVisibilityAnimation() + { + return _showVisibilityAnimation ??= CreateIsVisibleAnimation(Duration, true); + } + + private static Animation CreateIsVisibleAnimation(TimeSpan duration, bool endState) + { + return new() + { + Duration = duration, + FillMode = FillMode.Forward, + Children = + { + new KeyFrame() + { + Setters = + { + new Setter + { + Property = Visual.IsVisibleProperty, + Value = true, + }, + }, + Cue = new Cue(0) + }, + new KeyFrame() + { + Setters = + { + new Setter + { + Property = Visual.IsVisibleProperty, + Value = endState, + }, + }, + Cue = new Cue(1) + } + } + }; + } + } +} diff --git a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs index a8dea149b0..1e6a302d9f 100644 --- a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs @@ -1,26 +1,33 @@ using System; -using System.Threading; -using System.Threading.Tasks; +using Avalonia.Animation.Easings; using Avalonia.Media; using Avalonia.Styling; namespace Avalonia.Animation; -public class Rotate3DTransition: PageSlide +public class Rotate3DTransition : PageTransition { + /// + /// Initializes a new instance of the + /// + public Rotate3DTransition() + : this(TimeSpan.Zero) + { + } /// - /// Creates a new instance of the + /// Initializes a new instance of the /// /// How long the rotation should take place /// The orientation of the rotation /// Defines the depth of the 3D Effect. If null, depth will be calculated automatically from the width or height of the common parent of the visual being rotated - public Rotate3DTransition(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal, double? depth = null) - : base(duration, orientation) + public Rotate3DTransition(TimeSpan duration, PageSlideAxis orientation = PageSlideAxis.Horizontal, double? depth = null) + : base(duration) { + Orientation = orientation; Depth = depth; } - + /// /// Defines the depth of the 3D Effect. If null, depth will be calculated automatically from the width or height /// of the common parent of the visual being rotated. @@ -28,33 +35,67 @@ public class Rotate3DTransition: PageSlide public double? Depth { get; set; } /// - /// Creates a new instance of the + /// Gets the direction of the animation. /// - public Rotate3DTransition() { } + public PageSlideAxis Orientation { get; set; } - /// - public override async Task Start(Visual? @from, Visual? to, bool forward, CancellationToken cancellationToken) + /// + /// Gets or sets element entrance easing. + /// + public Easing SlideInEasing { get; set; } = new LinearEasing(); + + /// + /// Gets or sets element exit easing. + /// + public Easing SlideOutEasing { get; set; } = new LinearEasing(); + + protected override Animation GetHideAnimation(Visual? from, Visual? to, bool forward) { - if (cancellationToken.IsCancellationRequested) + var parent = PageSlide.GetVisualParent(from, to); + return new Animation { - return; - } + Easing = SlideOutEasing, + Duration = Duration, + Children = + { + CreateKeyFrame(parent, 0d, 0d, 2), + CreateKeyFrame(parent, 0.5d, 45d * (forward ? -1 : 1), 1), + CreateKeyFrame(parent, 1d, 90d * (forward ? -1 : 1), 1, isVisible: false) + }, + }; + } + + protected override Animation GetShowAnimation(Visual? from, Visual? to, bool forward) + { + var parent = PageSlide.GetVisualParent(from, to); + return new Animation + { + Easing = SlideInEasing, + Duration = Duration, + Children = + { + CreateKeyFrame(parent, 0d, 90d * (forward ? 1 : -1), 1), + CreateKeyFrame(parent, 0.5d, 45d * (forward ? 1 : -1), 1), + CreateKeyFrame(parent, 1d, 0d, 2) + }, + }; + } - var tasks = new Task[from != null && to != null ? 2 : 1]; - var parent = GetVisualParent(from, to); + private KeyFrame CreateKeyFrame(Visual parent, double cue, double rotation, int zIndex, bool isVisible = true) + { var (rotateProperty, center) = Orientation switch { - SlideAxis.Vertical => (Rotate3DTransform.AngleXProperty, parent.Bounds.Height), - SlideAxis.Horizontal => (Rotate3DTransform.AngleYProperty, parent.Bounds.Width), + PageSlideAxis.Vertical => (Rotate3DTransform.AngleXProperty, parent.Bounds.Height), + PageSlideAxis.Horizontal => (Rotate3DTransform.AngleYProperty, parent.Bounds.Width), _ => throw new ArgumentOutOfRangeException() }; - var depthSetter = new Setter {Property = Rotate3DTransform.DepthProperty, Value = Depth ?? center}; - var centerZSetter = new Setter {Property = Rotate3DTransform.CenterZProperty, Value = -center / 2}; + var depthSetter = new Setter { Property = Rotate3DTransform.DepthProperty, Value = Depth ?? center }; + var centerZSetter = new Setter { Property = Rotate3DTransform.CenterZProperty, Value = -center / 2 }; - KeyFrame CreateKeyFrame(double cue, double rotation, int zIndex, bool isVisible = true) => - new() { - Setters = + return new() + { + Setters = { new Setter { Property = Visual.IsVisibleProperty, Value = isVisible }, new Setter { Property = rotateProperty, Value = rotation }, @@ -62,62 +103,7 @@ public class Rotate3DTransition: PageSlide centerZSetter, depthSetter }, - Cue = new Cue(cue) - }; - - var initialFromVisible = true; - var initialToVisible = true; - - if (from != null) - { - var animation = new Animation - { - Easing = SlideOutEasing, - Duration = Duration, - Children = - { - CreateKeyFrame(0d, 0d, 2), - CreateKeyFrame(0.5d, 45d * (forward ? -1 : 1), 1), - CreateKeyFrame(1d, 90d * (forward ? -1 : 1), 1, isVisible: false) - } - }; - - tasks[0] = animation.RunAsync(from, null, cancellationToken); - - // Make "from" control invisible: this is overridden in the fade out animation, so - // will only take effect when the animation is completed. - initialFromVisible = from.IsVisible; - from.IsVisible = false; - } - - if (to != null) - { - to.IsVisible = true; - var animation = new Animation - { - Easing = SlideInEasing, - Duration = Duration, - Children = - { - CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1), - CreateKeyFrame(0.5d, 45d * (forward ? 1 : -1), 1), - CreateKeyFrame(1d, 0d, 2) - } - }; - - initialToVisible = to.IsVisible; - to.IsVisible = true; - tasks[from != null ? 1 : 0] = animation.RunAsync(to, null, cancellationToken); - } - - await Task.WhenAll(tasks); - - if (cancellationToken.IsCancellationRequested) - { - if (from != null) - from.IsVisible = initialFromVisible; - if (to != null) - to.IsVisible = initialToVisible; - } + Cue = new Cue(cue) + }; } } diff --git a/tests/Avalonia.Base.UnitTests/Animation/CrossFadeTests.cs b/tests/Avalonia.Base.UnitTests/Animation/CrossFadeTests.cs index 25cfcd312b..0bc834ff6a 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/CrossFadeTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/CrossFadeTests.cs @@ -69,6 +69,8 @@ namespace Avalonia.Base.UnitTests.Animation from.PropertyChanged += (s, e) => { + if (e.Property == Visual.IsVisibleProperty) + fromState.Add((to.Opacity, e.GetNewValue())); if (e.Property == Visual.OpacityProperty) fromState.Add((e.GetNewValue(), from.IsVisible)); }; @@ -93,9 +95,10 @@ namespace Avalonia.Base.UnitTests.Animation // Run the last frame. clock.Pulse(TimeSpan.FromMilliseconds(time)); - // Check that opacity is reset to default value (1.0) but control is not visible. - Assert.Equal(10, fromState.Count); - Assert.Equal((1.0, false), fromState[9]); + // Control should be hidden before transparency being reset. + Assert.Equal(11, fromState.Count); + Assert.Equal((0.9, false), fromState[9]); + Assert.Equal((1.0, false), fromState[10]); } [Fact] @@ -169,8 +172,13 @@ namespace Avalonia.Base.UnitTests.Animation time += 100; } - // First frame should be of a fully transparent visible control. - Assert.Equal((0.0, true), toState[0]); + // Control should be made transparent before shown. + Assert.Equal((0.0, false), toState[0]); + Assert.Equal((0.0, true), toState[1]); + + // Control should remain visible. + Assert.True(task.IsCompleted); + Assert.True(to.IsVisible); } private static IDisposable Start() diff --git a/tests/Avalonia.Base.UnitTests/Animation/PageSlideTests.cs b/tests/Avalonia.Base.UnitTests/Animation/PageSlideTests.cs index 722ffc99e0..f2d965a9c8 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/PageSlideTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/PageSlideTests.cs @@ -60,6 +60,8 @@ namespace Avalonia.Base.UnitTests.Animation from.PropertyChanged += (s, e) => { + if (e.Property == Visual.IsVisibleProperty) + fromState.Add((from.GetValue(TranslateTransform.XProperty), e.GetNewValue())); if (e.Property == TranslateTransform.XProperty) fromState.Add((e.GetNewValue(), from.IsVisible)); }; @@ -84,9 +86,10 @@ namespace Avalonia.Base.UnitTests.Animation // Run the last frame. clock.Pulse(TimeSpan.FromMilliseconds(time)); - // Check that X translate is reset to default value (0.0) but control is not visible. - Assert.Equal(10, fromState.Count); - Assert.Equal((0.0, false), fromState[9]); + // Control should be hidden before translate being reset. + Assert.Equal(11, fromState.Count); + Assert.Equal((-900.0, false), fromState[9]); + Assert.Equal((0.0, false), fromState[10]); } [Fact]