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]