159 lines
5.7 KiB
C#
159 lines
5.7 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Input;
|
|
using Avalonia.Rendering.Composition;
|
|
|
|
namespace Photos.Animations;
|
|
|
|
public static class CardHoverEffectBehavior
|
|
{
|
|
private static readonly TimeSpan _animationDuration = TimeSpan.FromMilliseconds(150);
|
|
|
|
public static readonly AttachedProperty<bool> IsEnabledProperty =
|
|
AvaloniaProperty.RegisterAttached<Control, bool>(
|
|
"IsEnabled",
|
|
typeof(CardHoverEffectBehavior),
|
|
false);
|
|
|
|
public static readonly AttachedProperty<double> MaxRotationAngleProperty =
|
|
AvaloniaProperty.RegisterAttached<Control, double>(
|
|
"MaxRotationAngle",
|
|
typeof(CardHoverEffectBehavior),
|
|
5.5);
|
|
|
|
public static readonly AttachedProperty<double> ScaleFactorProperty =
|
|
AvaloniaProperty.RegisterAttached<Control, double>(
|
|
"ScaleFactor",
|
|
typeof(CardHoverEffectBehavior),
|
|
0.99);
|
|
|
|
static CardHoverEffectBehavior()
|
|
{
|
|
IsEnabledProperty.Changed.AddClassHandler<Control>(OnIsEnabledChanged);
|
|
}
|
|
|
|
public static bool GetIsEnabled(AvaloniaObject obj) => obj.GetValue(IsEnabledProperty);
|
|
public static void SetIsEnabled(AvaloniaObject obj, bool value) => obj.SetValue(IsEnabledProperty, value);
|
|
|
|
public static double GetMaxRotationAngle(AvaloniaObject obj) => obj.GetValue(MaxRotationAngleProperty);
|
|
public static void SetMaxRotationAngle(AvaloniaObject obj, double value) => obj.SetValue(MaxRotationAngleProperty, value);
|
|
|
|
public static double GetScaleFactor(AvaloniaObject obj) => obj.GetValue(ScaleFactorProperty);
|
|
public static void SetScaleFactor(AvaloniaObject obj, double value) => obj.SetValue(ScaleFactorProperty, value);
|
|
|
|
private static void OnIsEnabledChanged(Control c, AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
if (e.NewValue is not bool enabled)
|
|
return;
|
|
|
|
if (enabled)
|
|
{
|
|
c.PointerEntered += Element_PointerEntered;
|
|
c.PointerMoved += Element_PointerMoved;
|
|
c.PointerExited += Element_PointerExited;
|
|
}
|
|
else
|
|
{
|
|
c.PointerEntered -= Element_PointerEntered;
|
|
c.PointerMoved -= Element_PointerMoved;
|
|
c.PointerExited -= Element_PointerExited;
|
|
}
|
|
}
|
|
|
|
private static void Element_PointerEntered(object? sender, PointerEventArgs e)
|
|
{
|
|
if (sender is not Control element)
|
|
return;
|
|
|
|
element.ZIndex = 1;
|
|
var visual = ElementComposition.GetElementVisual(element);
|
|
var compositor = visual?.Compositor;
|
|
|
|
if (visual is null || compositor is null)
|
|
return;
|
|
|
|
var scaleFactor = (float)GetScaleFactor(element);
|
|
|
|
var width = (float)element.Bounds.Width;
|
|
var height = (float)element.Bounds.Height;
|
|
|
|
if (width <= 0 || height <= 0)
|
|
return;
|
|
|
|
visual.CenterPoint = new Vector3(width / 2f, height / 2f, 0f);
|
|
|
|
var scaleAnimation = compositor.CreateVector3KeyFrameAnimation();
|
|
scaleAnimation.InsertKeyFrame(1.0f, new Vector3(scaleFactor, scaleFactor, 1f));
|
|
scaleAnimation.Duration = _animationDuration;
|
|
|
|
var orientationAnimation = compositor.CreateQuaternionKeyFrameAnimation();
|
|
orientationAnimation.InsertKeyFrame(1.0f, Quaternion.Identity);
|
|
orientationAnimation.Duration = _animationDuration;
|
|
|
|
visual.StartAnimation("Scale", scaleAnimation);
|
|
visual.StartAnimation(nameof(visual.Orientation), orientationAnimation);
|
|
}
|
|
|
|
private static void Element_PointerMoved(object? sender, PointerEventArgs e)
|
|
{
|
|
if (sender is not Control element)
|
|
return;
|
|
|
|
var visual = ElementComposition.GetElementVisual(element);
|
|
|
|
if (visual is null)
|
|
return;
|
|
|
|
var maxRotationAngle = (float)GetMaxRotationAngle(element);
|
|
|
|
var width = element.Bounds.Width;
|
|
var height = element.Bounds.Height;
|
|
|
|
if (width <= 0 || height <= 0)
|
|
return;
|
|
|
|
var position = e.GetPosition(element);
|
|
var centerX = width / 2.0;
|
|
var centerY = height / 2.0;
|
|
|
|
var normalizedX = (position.X - centerX) / centerX;
|
|
var normalizedY = (position.Y - centerY) / centerY;
|
|
|
|
var rotationY_deg = (float)(normalizedX * maxRotationAngle);
|
|
var rotationX_deg = (float)(-normalizedY * maxRotationAngle);
|
|
|
|
var rotationX_rad = (float)(Math.PI / 180.0 * rotationX_deg);
|
|
var rotationY_rad = (float)(Math.PI / 180.0 * rotationY_deg);
|
|
|
|
var qx = Quaternion.CreateFromAxisAngle(Vector3.UnitX, rotationX_rad);
|
|
var qy = Quaternion.CreateFromAxisAngle(Vector3.UnitY, rotationY_rad);
|
|
|
|
visual.Orientation = qy * qx;
|
|
}
|
|
|
|
private static void Element_PointerExited(object? sender, PointerEventArgs e)
|
|
{
|
|
if (sender is not Control element)
|
|
return;
|
|
|
|
element.ZIndex = 0;
|
|
var visual = ElementComposition.GetElementVisual(element);
|
|
var compositor = visual?.Compositor;
|
|
|
|
if (visual is null || compositor is null)
|
|
return;
|
|
|
|
var scaleAnimation = compositor.CreateVector3KeyFrameAnimation();
|
|
scaleAnimation.InsertKeyFrame(1.0f, new Vector3(1f, 1f, 1f));
|
|
scaleAnimation.Duration = _animationDuration;
|
|
|
|
var orientationAnimation = compositor.CreateQuaternionKeyFrameAnimation();
|
|
orientationAnimation.InsertKeyFrame(1.0f, Quaternion.Identity);
|
|
orientationAnimation.Duration = _animationDuration;
|
|
|
|
visual.StartAnimation("Scale", scaleAnimation);
|
|
visual.StartAnimation(nameof(visual.Orientation), orientationAnimation);
|
|
}
|
|
} |