diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml b/samples/ControlCatalog/Pages/CarouselPage.xaml
index 4a53c9026f..1c2d768966 100644
--- a/samples/ControlCatalog/Pages/CarouselPage.xaml
+++ b/samples/ControlCatalog/Pages/CarouselPage.xaml
@@ -29,6 +29,7 @@
None
Slide
Crossfade
+ 3D Rotation
diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs
index 66180d4ccb..6b7707be13 100644
--- a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs
@@ -45,6 +45,9 @@ namespace ControlCatalog.Pages
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);
+ break;
}
}
}
diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs
index b5a6062593..6fa0b4fb9c 100644
--- a/src/Avalonia.Visuals/Animation/PageSlide.cs
+++ b/src/Avalonia.Visuals/Animation/PageSlide.cs
@@ -10,7 +10,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Animation
{
///
- /// Transitions between two pages by sliding them horizontally.
+ /// Transitions between two pages by sliding them horizontally or vertically.
///
public class PageSlide : IPageTransition
{
@@ -62,7 +62,7 @@ namespace Avalonia.Animation
public Easing SlideOutEasing { get; set; } = new LinearEasing();
///
- public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
+ public virtual async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
@@ -155,7 +155,7 @@ namespace Avalonia.Animation
///
/// Any one of the parameters may be null, but not both.
///
- private static IVisual GetVisualParent(IVisual? from, IVisual? to)
+ protected static IVisual GetVisualParent(IVisual? from, IVisual? to)
{
var p1 = (from ?? to)!.VisualParent;
var p2 = (to ?? from)!.VisualParent;
diff --git a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs
new file mode 100644
index 0000000000..b603fe5f7b
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Media;
+using Avalonia.Styling;
+
+namespace Avalonia.Animation;
+
+public class Rotate3DTransition: PageSlide
+{
+
+ ///
+ /// Creates a new instance if the
+ ///
+ /// How long the rotation should take place
+ /// The orientation of the rotation
+ public Rotate3DTransition(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal)
+ : base(duration, orientation)
+ {}
+
+ ///
+ /// Creates a new instance if the
+ ///
+ public Rotate3DTransition() { }
+
+ ///
+ public override async Task Start(Visual? @from, Visual? to, bool forward, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ var tasks = new List();
+ var parent = GetVisualParent(from, to);
+ var (rotateProperty, center) = Orientation switch
+ {
+ SlideAxis.Vertical => (Rotate3DTransform.AngleXProperty, parent.Bounds.Height),
+ SlideAxis.Horizontal => (Rotate3DTransform.AngleYProperty, parent.Bounds.Width),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ var depthSetter = new Setter {Property = Rotate3DTransform.DepthProperty, Value = center};
+ var centerZSetter = new Setter {Property = Rotate3DTransform.CenterZProperty, Value = -center / 2};
+
+ if (from != null)
+ {
+ var animation = new Animation
+ {
+ Duration = Duration,
+ Children =
+ {
+ new KeyFrame
+ {
+ Setters =
+ {
+ new Setter { Property = rotateProperty, Value = 0d },
+ new Setter { Property = Visual.ZIndexProperty, Value = 2 },
+ centerZSetter,
+ depthSetter,
+ },
+ Cue = new Cue(0d)
+ },
+ new KeyFrame
+ {
+ Setters =
+ {
+ new Setter { Property = rotateProperty, Value = 45d * (forward ? -1 : 1) },
+ new Setter { Property = Visual.ZIndexProperty, Value = 1 },
+ centerZSetter,
+ depthSetter
+ },
+ Cue = new Cue(0.5d)
+ },
+ new KeyFrame
+ {
+ Setters =
+ {
+ new Setter { Property = rotateProperty, Value = 90d * (forward ? -1 : 1) },
+ new Setter { Property = Visual.ZIndexProperty, Value = 1 },
+ centerZSetter,
+ depthSetter
+ },
+ Cue = new Cue(1d)
+ }
+ }
+ };
+
+ tasks.Add(animation.RunAsync(from, null, cancellationToken));
+ }
+
+ if (to != null)
+ {
+ to.IsVisible = true;
+ var animation = new Animation
+ {
+ Duration = Duration,
+ Children =
+ {
+ new KeyFrame
+ {
+ Setters =
+ {
+ new Setter { Property = rotateProperty, Value = 90d * (forward ? 1 : -1) },
+ new Setter { Property = Visual.ZIndexProperty, Value = 1 },
+ centerZSetter,
+ depthSetter
+ },
+ Cue = new Cue(0d)
+ },
+ new KeyFrame
+ {
+ Setters =
+ {
+ new Setter { Property = Visual.ZIndexProperty, Value = 1 },
+ new Setter { Property = rotateProperty, Value = 45d * (forward ? 1 : -1) },
+ centerZSetter,
+ depthSetter
+ },
+ Cue = new Cue(0.5d)
+ },
+ new KeyFrame
+ {
+ Setters =
+ {
+ new Setter { Property = rotateProperty, Value = 0d },
+ new Setter { Property = Visual.ZIndexProperty, Value = 2 },
+ centerZSetter,
+ depthSetter,
+ },
+ Cue = new Cue(1d)
+ }
+ }
+ };
+
+ tasks.Add(animation.RunAsync(to, null, cancellationToken));
+ }
+
+ await Task.WhenAll(tasks);
+
+ if (from != null && !cancellationToken.IsCancellationRequested)
+ {
+ from.IsVisible = false;
+ from.ZIndex = 1;
+ }
+
+ if (to != null && !cancellationToken.IsCancellationRequested)
+ {
+ to.ZIndex = 2;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs
index 3b8cc57b45..0fea9d73a0 100644
--- a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs
+++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs
@@ -9,6 +9,8 @@ namespace Avalonia.Media;
///
public class Rotate3DTransform : Transform
{
+ private readonly bool _isInitializing;
+
///
/// Defines the property.
///
@@ -76,12 +78,14 @@ public class Rotate3DTransform : Transform
double centerY,
double centerZ) : this()
{
+ _isInitializing = true;
AngleX = angleX;
AngleY = angleY;
AngleZ = angleZ;
CenterX = centerX;
CenterY = centerY;
CenterZ = centerZ;
+ _isInitializing = false;
}
///
@@ -148,21 +152,22 @@ public class Rotate3DTransform : Transform
}
///
- /// Gets the transform's .
+ /// Gets the transform's .
///
public override Matrix Value
{
get
{
var matrix44 = Matrix4x4.Identity;
+ var centerSum = CenterX + CenterY + CenterZ;
+
+ if (centerSum != 0) matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ);
- matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ);
+ if (AngleX != 0) matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(AngleX));
+ if (AngleY != 0) matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(AngleY));
+ if (AngleZ != 0) matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(AngleZ));
- matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(AngleX));
- matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(AngleY));
- matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(AngleZ));
-
- matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ);
+ if (centerSum != 0) matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ);
if (Depth != 0)
{
@@ -186,5 +191,8 @@ public class Rotate3DTransform : Transform
}
}
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) => RaiseChanged();
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ if (!_isInitializing) RaiseChanged();
+ }
}