Refactor Tooltip Motion

This commit is contained in:
polarboy 2024-10-07 22:43:03 +08:00
parent b521c64a99
commit 369501d400
11 changed files with 241 additions and 157 deletions

View File

@ -35,24 +35,24 @@ internal class AbstractMotion : IMotion
{
using var originRestore = new RenderTransformOriginRestore(actor);
actor.RenderTransformOrigin = RenderTransformOrigin;
actor.NotifyMotionPreStart();
NotifyPreStart();
if (aboutToStart is not null)
{
aboutToStart();
}
actor.NotifyMotionPreStart();
NotifyPreStart();
actor.IsVisible = true;
foreach (var animation in Animations)
{
await animation.RunAsync(actor, cancellationToken);
}
actor.NotifyMotionCompleted();
NotifyCompleted();
if (completedAction is not null)
{
completedAction();
}
actor.NotifyMotionCompleted();
NotifyCompleted();
});
}

View File

@ -3,4 +3,5 @@
internal interface INotifyCaptureGhostBitmap
{
public void NotifyCaptureGhostBitmap();
public void NotifyClearGhostBitmap();
}

View File

@ -44,32 +44,32 @@ internal static class MotionInvoker
Action? completedAction = null,
CancellationToken cancellationToken = default)
{
actor.BuildGhost();
SceneLayer sceneLayer = PrepareSceneLayer(motion, actor);
var compositeDisposable = new CompositeDisposable();
compositeDisposable.Add(Disposable.Create(sceneLayer, (state) =>
{
Dispatcher.UIThread.Invoke(async () =>
{
await Task.Delay(300);
await Task.Delay(100);
sceneLayer.Hide();
sceneLayer.Dispose();
});
}));
var ghost = actor.GetAnimatableGhost();
sceneLayer.SetMotionTarget(ghost);
actor.NotifyMotionTargetAddedToScene(ghost);
sceneLayer.SetMotionActor(actor);
actor.NotifyMotionTargetAddedToScene();
sceneLayer.Show();
sceneLayer.Topmost = true;
actor.NotifySceneShowed();
actor.IsVisible = false;
await motion.RunAsync(actor, aboutToStart, () =>
{
compositeDisposable.Dispose();
if (completedAction is not null)
{
completedAction();
}
compositeDisposable.Dispose();
}, cancellationToken);
}

View File

@ -88,6 +88,16 @@ internal class MoveDownInMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithHeight(actorSize.Height * 2);
}
internal override Point CalculateScenePosition(Size actorSize, Point actorPosition)
{
return actorPosition.WithY(actorPosition.Y + actorSize.Height);
}
}
internal class MoveDownOutMotion : AbstractMotion
@ -173,6 +183,11 @@ internal class MoveDownOutMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithHeight(actorSize.Height * 2);
}
}
internal class MoveUpInMotion : AbstractMotion
@ -257,6 +272,16 @@ internal class MoveUpInMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithHeight(actorSize.Height * 2);
}
internal override Point CalculateScenePosition(Size actorSize, Point actorPosition)
{
return actorPosition.WithY(actorPosition.Y - actorSize.Height);
}
}
internal class MoveUpOutMotion : AbstractMotion
@ -351,6 +376,11 @@ internal class MoveUpOutMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithHeight(actorSize.Height * 2);
}
}
internal class MoveLeftInMotion : AbstractMotion
@ -438,6 +468,16 @@ internal class MoveLeftInMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithWidth(actorSize.Width * 2);
}
internal override Point CalculateScenePosition(Size actorSize, Point actorPosition)
{
return actorPosition.WithX(actorPosition.X - actorSize.Width);
}
}
internal class MoveLeftOutMotion : AbstractMotion
@ -521,6 +561,11 @@ internal class MoveLeftOutMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithHeight(actorSize.Width * 2);
}
}
internal class MoveRightInMotion : AbstractMotion
@ -606,6 +651,16 @@ internal class MoveRightInMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithHeight(actorSize.Width * 2);
}
internal override Point CalculateScenePosition(Size actorSize, Point actorPosition)
{
return actorPosition.WithY(actorPosition.X + actorSize.Height);
}
}
internal class MoveRightOutMotion : AbstractMotion
@ -690,4 +745,9 @@ internal class MoveRightOutMotion : AbstractMotion
Animations.Add(animation);
}
internal override Size CalculateSceneSize(Size actorSize)
{
return actorSize.WithHeight(actorSize.Width * 2);
}
}

View File

@ -14,6 +14,7 @@ internal class SceneLayer : WindowBase, IHostedVisualTreeRoot, IDisposable
private readonly IManagedPopupPositionerPopup? _managedPopupPositionerPopup;
private static readonly FieldInfo ManagedPopupPositionerPopupInfo;
private readonly Canvas _layout;
private SceneMotionActorControl? _motionActorControl;
static SceneLayer()
{
@ -88,9 +89,10 @@ internal class SceneLayer : WindowBase, IHostedVisualTreeRoot, IDisposable
PlatformImpl?.Dispose();
}
public void SetMotionTarget(Control motionTarget)
public void SetMotionActor(SceneMotionActorControl actorControl)
{
_layout.Children.Add(motionTarget);
_motionActorControl = actorControl;
_layout.Children.Add(actorControl);
}
// 这个地方我们可以需要定制
@ -149,12 +151,9 @@ internal class SceneLayer : WindowBase, IHostedVisualTreeRoot, IDisposable
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
foreach (var child in _layout.Children)
if (_motionActorControl is not null)
{
if (child is INotifyCaptureGhostBitmap captureGhost)
{
captureGhost.NotifyCaptureGhostBitmap();
}
_motionActorControl.NotifySceneLayerHostWinOpened();
}
}
}

View File

@ -7,6 +7,15 @@ namespace AtomUI.MotionScene;
internal class SceneMotionActorControl : MotionActorControl
{
public event EventHandler? SceneShowed;
#region
/// <summary>
/// 动画实体
/// </summary>
public Control MotionTarget { get; set; }
#endregion
#region
@ -18,8 +27,14 @@ internal class SceneMotionActorControl : MotionActorControl
#endregion
protected Control? _ghost;
public SceneMotionActorControl(Control motionTarget)
{
MotionTarget = motionTarget;
UseRenderTransform = true;
}
protected virtual void BuildGhost()
internal virtual void BuildGhost()
{
}
@ -36,6 +51,7 @@ internal class SceneMotionActorControl : MotionActorControl
/// <summary>
/// 在这个接口中Actor 根据自己的需求对 sceneLayer 进行设置,主要就是位置和大小
/// </summary>
/// <param name="motion"></param>
/// <param name="sceneLayer"></param>
public virtual void NotifySceneLayerCreated(AbstractMotion motion, SceneLayer sceneLayer)
{
@ -61,15 +77,31 @@ internal class SceneMotionActorControl : MotionActorControl
/// <summary>
/// 当动画目标控件被添加到动画场景中之后调用,这里需要根据 Motion 的种类设置初始位置和大小
/// </summary>
/// <param name="motionTarget"></param>
public virtual void NotifyMotionTargetAddedToScene(Control motionTarget)
public virtual void NotifyMotionTargetAddedToScene()
{
Canvas.SetLeft(motionTarget, 0);
Canvas.SetTop(motionTarget, 0);
Canvas.SetLeft(this, 0);
Canvas.SetTop(this, 0);
}
public virtual void NotifySceneShowed()
{
SceneShowed?.Invoke(this, EventArgs.Empty);
}
internal virtual void NotifySceneLayerHostWinOpened()
{
if (_ghost is INotifyCaptureGhostBitmap captureGhost)
{
captureGhost.NotifyCaptureGhostBitmap();
}
}
internal override void NotifyMotionCompleted()
{
base.NotifyMotionCompleted();
if (_ghost is INotifyCaptureGhostBitmap notifyCaptureGhostBitmap)
{
notifyCaptureGhostBitmap.NotifyClearGhostBitmap();
}
}
}

View File

@ -151,7 +151,7 @@ internal class ZoomBigInMotion : AbstractMotion
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = BuildScaleTransform(0.8)
Value = BuildScaleTransform(0.85)
};
startFrame.Setters.Add(transformSetter);
}
@ -177,7 +177,7 @@ internal class ZoomBigInMotion : AbstractMotion
endFrame.Setters.Add(transformSetter);
}
animation.Children.Add(endFrame);
RenderTransformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
RenderTransformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
Animations.Add(animation);
}
}
@ -231,12 +231,12 @@ internal class ZoomBigOutMotion : AbstractMotion
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = BuildScaleTransform(0.8)
Value = BuildScaleTransform(0.85)
};
endFrame.Setters.Add(transformSetter);
}
animation.Children.Add(endFrame);
RenderTransformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
RenderTransformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
Animations.Add(animation);
}
}

View File

@ -1,10 +1,12 @@
using System.Reactive.Disposables;
using AtomUI.Data;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.Input.Raw;
@ -78,7 +80,7 @@ public class Popup : AvaloniaPopup
AffectsMeasure<Popup>(PlacementProperty);
AffectsMeasure<Popup>(PlacementAnchorProperty);
AffectsMeasure<Popup>(PlacementGravityProperty);
IsLightDismissEnabledProperty.OverrideDefaultValue<Popup>(false);
}
@ -159,7 +161,7 @@ public class Popup : AvaloniaPopup
AdjustPopupHostPosition(placementTarget!);
}
}
if (!_animating)
{
CreateShadowLayer();
@ -173,15 +175,16 @@ public class Popup : AvaloniaPopup
}
}
}
private bool _firstDetected = true;
private void HandleMouseClick(RawInputEventArgs args)
{
if (!IsOpen)
{
return;
}
if (args is RawPointerEventArgs pointerEventArgs)
{
if (pointerEventArgs.Type == RawPointerEventType.LeftButtonUp)
@ -191,6 +194,7 @@ public class Popup : AvaloniaPopup
_firstDetected = false;
return;
}
if (this is IPopupHostProvider popupHostProvider)
{
if (popupHostProvider.PopupHost != pointerEventArgs.Root)
@ -320,10 +324,10 @@ public class Popup : AvaloniaPopup
new ManagedPopupPositionerScreenInfo(s.Bounds.ToRect(1), s.WorkingArea.ToRect(1)))
.ToArray();
}
return Array.Empty<ManagedPopupPositionerScreenInfo>();
}
// TODO review 后可能需要删除
private static Rect GetParentClientAreaScreenGeometry(TopLevel topLevel)
{
@ -461,49 +465,41 @@ public class Popup : AvaloniaPopup
{
return;
}
Open();
return;
// _animating = true;
//
// var placementTarget = GetEffectivePlacementTarget();
//
// Open();
//
// var popupRoot = (Host as PopupRoot)!;
// // 获取 popup 的具体位置,这个就是非常准确的位置,还有大小
// // TODO 暂时只支持 WindowBase popup
// popupRoot.Hide();
// var popupOffset = popupRoot.PlatformImpl!.Position;
// var offset = new Point(popupOffset.X, popupOffset.Y);
// var topLevel = TopLevel.GetTopLevel(placementTarget);
// var scaling = topLevel?.RenderScaling ?? 1.0;
//
// // 调度动画
// var director = Director.Instance;
// var motion = new ZoomBigInMotion();
// motion.ConfigureOpacity(MotionDuration);
// motion.ConfigureRenderTransform(MotionDuration);
//
// var motionActor =
// new PopupMotionActor(MaskShadows, offset, scaling, Child ?? popupRoot, motion);
// motionActor.DispatchInSceneLayer = true;
// motionActor.SceneParent = topLevel;
//
// motionActor.Completed += (sender, args) =>
// {
// CreateShadowLayer();
// popupRoot.Show();
//
// if (RequestCloseWhereAnimationCompleted)
// {
// RequestCloseWhereAnimationCompleted = false;
// Dispatcher.UIThread.Post(() => { CloseAnimation(); });
// }
//
// _animating = false;
// };
// director?.Schedule(motionActor);
_animating = true;
var placementTarget = GetEffectivePlacementTarget();
Open();
var popupRoot = (Host as PopupRoot)!;
// 获取 popup 的具体位置,这个就是非常准确的位置,还有大小
// TODO 暂时只支持 WindowBase popup
popupRoot.Hide();
var popupOffset = popupRoot.PlatformImpl!.Position;
var offset = new Point(popupOffset.X, popupOffset.Y);
var topLevel = TopLevel.GetTopLevel(placementTarget);
var scaling = topLevel?.RenderScaling ?? 1.0;
// 调度动画
var motion = new ZoomBigInMotion(MotionDuration);
var motionActor = new PopupMotionActor(MaskShadows, offset, scaling, Child ?? popupRoot);
motionActor.SceneParent = topLevel;
MotionInvoker.InvokeInPopupLayer(motionActor, motion, null, () =>
{
CreateShadowLayer();
popupRoot.Show();
if (RequestCloseWhereAnimationCompleted)
{
RequestCloseWhereAnimationCompleted = false;
Dispatcher.UIThread.Post(() => { CloseAnimation(); });
}
_animating = false;
});
}
public void CloseAnimation(Action? closed = null)
@ -513,51 +509,41 @@ public class Popup : AvaloniaPopup
RequestCloseWhereAnimationCompleted = true;
return;
}
if (!IsOpen)
{
return;
}
Close();
// _animating = true;
//
// var director = Director.Instance;
// var motion = new ZoomBigOutMotion();
// motion.ConfigureOpacity(MotionDuration);
// motion.ConfigureRenderTransform(MotionDuration);
//
// var popupRoot = (Host as PopupRoot)!;
// var popupOffset = popupRoot.PlatformImpl!.Position;
// var offset = new Point(popupOffset.X, popupOffset.Y);
// var placementTarget = GetEffectivePlacementTarget();
// var topLevel = TopLevel.GetTopLevel(placementTarget);
//
// var scaling = topLevel?.RenderScaling ?? 1.0;
//
// var motionActor = new PopupMotionActor(MaskShadows, offset, scaling, Child ?? popupRoot, motion);
// motionActor.DispatchInSceneLayer = true;
// motionActor.SceneParent = topLevel;
//
// motionActor.SceneShowed += (sender, args) =>
// {
// HideShadowLayer();
// popupRoot.Opacity = 0;
// };
//
// motionActor.Completed += (sender, args) =>
// {
// _animating = false;
// _isNeedFlip = true;
// Close();
// if (closed is not null)
// {
// closed();
// }
// };
//
// director?.Schedule(motionActor);
_animating = true;
var motion = new ZoomBigOutMotion(MotionDuration);
var popupRoot = (Host as PopupRoot)!;
var popupOffset = popupRoot.PlatformImpl!.Position;
var offset = new Point(popupOffset.X, popupOffset.Y);
var placementTarget = GetEffectivePlacementTarget();
var topLevel = TopLevel.GetTopLevel(placementTarget);
var scaling = topLevel?.RenderScaling ?? 1.0;
var motionActor = new PopupMotionActor(MaskShadows, offset, scaling, Child ?? popupRoot);
motionActor.SceneParent = topLevel;
MotionInvoker.InvokeInPopupLayer(motionActor, motion, () =>
{
HideShadowLayer();
popupRoot.Opacity = 0;
}, () =>
{
_animating = false;
_isNeedFlip = true;
Close();
if (closed is not null)
{
closed();
}
});
}
}

View File

@ -7,40 +7,40 @@ using Avalonia.Media;
namespace AtomUI.Controls;
// internal class PopupMotionActor : MotionActor
// {
// private readonly BoxShadows _boxShadows;
// private readonly Point _offset;
// private readonly double _scaling;
//
// public PopupMotionActor(BoxShadows boxShadows,
// Point offset,
// double scaling,
// Control motionTarget,
// AbstractMotion motion)
// : base(motionTarget, motion)
// {
// _offset = offset;
// _scaling = scaling;
// _boxShadows = boxShadows;
// }
//
// protected override Point CalculateTopLevelGhostPosition()
// {
// var boxShadowsThickness = _boxShadows.Thickness();
// var winPos = _offset; // TODO 可能需要乘以 scaling
// var scaledThickness = boxShadowsThickness * _scaling;
// return new Point(winPos.X - scaledThickness.Left, winPos.Y - scaledThickness.Top);
// }
//
// protected override void BuildGhost()
// {
// if (_ghost is null)
// {
// _ghost = new MotionGhostControl(MotionTarget, _boxShadows)
// {
// Shadows = _boxShadows
// };
// }
// }
// }
internal class PopupMotionActor : SceneMotionActorControl
{
private readonly BoxShadows _boxShadows;
private readonly Point _offset;
private readonly double _scaling;
public PopupMotionActor(BoxShadows boxShadows,
Point offset,
double scaling,
Control motionTarget)
: base(motionTarget)
{
_offset = offset;
_scaling = scaling;
_boxShadows = boxShadows;
}
protected override Point CalculateTopLevelGhostPosition()
{
var boxShadowsThickness = _boxShadows.Thickness();
var winPos = _offset; // TODO 可能需要乘以 scaling
var scaledThickness = boxShadowsThickness * _scaling;
return new Point(winPos.X - scaledThickness.Left, winPos.Y - scaledThickness.Top);
}
internal override void BuildGhost()
{
if (_ghost is null)
{
_ghost = new MotionGhostControl(MotionTarget, _boxShadows)
{
Shadows = _boxShadows
};
Child = _ghost;
}
}
}

View File

@ -149,7 +149,7 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
offsetX += _maskOffset.X;
offsetY += _maskOffset.Y;
// 不知道这里为啥不行
var renderers = new List<Control>();
for (var i = 0; i < shadows.Count; ++i)
{
@ -217,4 +217,10 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
_layout!.Children.Clear();
}
}
public void NotifyClearGhostBitmap()
{
_ghostBitmap = null;
InvalidateVisual();
}
}

View File

@ -10,7 +10,6 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Threading;
namespace AtomUI.Controls;
@ -678,7 +677,7 @@ public class ToolTip : TemplatedControl, IShadowMaskInfoProvider
{
popupHostProvider.PopupHostChanged += HandlePopupHostChanged;
}
// TODO 可能是多余的,因为有那个对反转事件的处理
SetupArrowPosition(placement);
// 后期看能不能检测对应字段的改变
@ -728,6 +727,7 @@ public class ToolTip : TemplatedControl, IShadowMaskInfoProvider
{
return;
}
if (_popup is IPopupHostProvider popupHostProvider)
{
popupHostProvider.PopupHostChanged -= HandlePopupHostChanged;