优化popup 动画

This commit is contained in:
polarboy
2024-07-24 20:28:16 +08:00
parent 95d96006a9
commit 0448c014ac
9 changed files with 232 additions and 445 deletions
+36 -151
View File
@@ -1,8 +1,5 @@
using System.ComponentModel;
using System.Reactive.Disposables;
using AtomUI.Controls.MotionScene;
using AtomUI.Controls.Utils;
using AtomUI.MotionScene;
using AtomUI.Styling;
using AtomUI.Utils;
using Avalonia;
@@ -110,11 +107,6 @@ public class Flyout : PopupFlyoutBase
(o, v) => o._motionDuration = v);
private CompositeDisposable? _compositeDisposable;
private bool _animating = false;
// 当鼠标移走了,但是打开动画还没完成,我们需要记录下来这个信号
internal bool RequestCloseWhereAnimationCompleted { get; set; } = false;
private PopupPositionInfo? _popupPositionInfo; // 这个信息在隐藏动画的时候会用到
static Flyout()
{
@@ -291,80 +283,44 @@ public class Flyout : PopupFlyoutBase
protected override bool ShowAtCore(Control placementTarget, bool showAtPointer = false)
{
// if (_animating) {
// return false;
// }
//
// RequestCloseWhereAnimationCompleted = false;
// CalculateShowArrowEffective();
// var presenter = CreatePresenter();
// bool result = default;
// if (presenter is FlyoutPresenter flyoutPresenter) {
// _animating = true;
// if (flyoutPresenter.Child is not null) {
// var placementToplevel = TopLevel.GetTopLevel(placementTarget);
// UIStructureUtils.ClearLogicalParentRecursive(flyoutPresenter, null);
// UIStructureUtils.ClearVisualParentRecursive(flyoutPresenter, null);
// UIStructureUtils.SetLogicalParent(flyoutPresenter, placementToplevel);
//
// _popupPositionInfo = PopupControl.CalculatePositionInfo(placementTarget,
// MarginToAnchor,
// presenter,
// new Point(HorizontalOffset, VerticalOffset),
// Placement,
// Popup.PlacementAnchor,
// Popup.PlacementGravity,
// null,
// Popup.FlowDirection);
// // 重新设置箭头位置
// // 因为可能有 flip 的情况
// var arrowPosition = PopupUtils.CalculateArrowPosition(_popupPositionInfo.EffectivePlacement,
// _popupPositionInfo.EffectivePlacementAnchor,
// _popupPositionInfo.EffectivePlacementGravity);
// if (arrowPosition.HasValue) {
// flyoutPresenter.ArrowPosition = arrowPosition.Value;
// }
//
// // 获取是否在指向中点
// var pointAtCenterOffset = CalculatePopupPositionDelta(placementTarget,
// presenter,
// _popupPositionInfo.EffectivePlacement,
// _popupPositionInfo.EffectivePlacementAnchor,
// _popupPositionInfo.EffectivePlacementGravity);
// if (IsPointAtCenter) {
// _popupPositionInfo.Offset = new Point(
// Math.Floor(_popupPositionInfo.Offset.X + pointAtCenterOffset.X * _popupPositionInfo.Scaling),
// Math.Floor(_popupPositionInfo.Offset.Y + pointAtCenterOffset.Y * _popupPositionInfo.Scaling));
// }
//
// PlayShowMotion(_popupPositionInfo, placementTarget, flyoutPresenter, showAtPointer);
// }
//
// result = true;
// } else {
// result = base.ShowAtCore(placementTarget, showAtPointer);
// }
return base.ShowAtCore(placementTarget, showAtPointer);
if (IsOpen) {
return false;
}
if (!PrepareShowPopup(placementTarget, showAtPointer)) {
return false;
}
IsOpen = true;
Dispatcher.UIThread.InvokeAsync(async () =>
{
await Popup.OpenAnimationAsync();
HandlePopupOpened(placementTarget);
});
return true;
}
// protected override bool HideCore(bool canCancel = true)
// {
// // 在这里我们需要自己实现是否能关闭的逻辑了
// if (!IsOpen || _animating) {
// return false;
// }
//
// if (canCancel) {
// if (CancelClosing()) {
// return false;
// }
// }
//
// // 后期加上是否有动画的开关
// PlayHideMotion();
// return true;
// }
protected override bool HideCore(bool canCancel = true)
{
if (!IsOpen) {
return false;
}
if (canCancel) {
if (CancelClosing()) {
return false;
}
}
IsOpen = false;
Dispatcher.UIThread.InvokeAsync(async () =>
{
await Popup.CloseAnimationAsync();
HandlePopupClosed();
});
return true;
}
private bool CancelClosing()
{
@@ -372,75 +328,4 @@ public class Flyout : PopupFlyoutBase
OnClosing(eventArgs);
return eventArgs.Cancel;
}
private void PlayShowMotion(PopupPositionInfo positionInfo, Control placementTarget, FlyoutPresenter flyoutPresenter,
bool showAtPointer)
{
var director = Director.Instance;
var motion = new ZoomBigInMotion();
motion.ConfigureOpacity(_motionDuration);
motion.ConfigureRenderTransform(_motionDuration);
var topLevel = TopLevel.GetTopLevel(placementTarget);
var motionActor =
new PopupMotionActor(MaskShadows, positionInfo.Offset, positionInfo.Scaling, flyoutPresenter, motion);
motionActor.DispatchInSceneLayer = true;
motionActor.SceneParent = topLevel;
motionActor.Completed += (sender, args) =>
{
base.ShowAtCore(placementTarget, showAtPointer);
if (Popup.Host is WindowBase window) {
window.PlatformImpl!.SetTopmost(true);
}
_animating = false;
if (RequestCloseWhereAnimationCompleted) {
Dispatcher.UIThread.Post(() => { Hide(); });
}
};
director?.Schedule(motionActor);
}
private void PlayHideMotion()
{
var placementToplevel = TopLevel.GetTopLevel(Popup.PlacementTarget);
if (_popupPositionInfo is null ||
Popup.Child is null ||
placementToplevel is null) {
// 没有动画位置信息,直接关闭
base.HideCore(false);
return;
}
_animating = true;
var director = Director.Instance;
var motion = new ZoomBigOutMotion();
motion.ConfigureOpacity(_motionDuration);
motion.ConfigureRenderTransform(_motionDuration);
UIStructureUtils.SetVisualParent(Popup.Child, null);
UIStructureUtils.SetVisualParent(Popup.Child, null);
var motionActor = new PopupMotionActor(MaskShadows, _popupPositionInfo.Offset, _popupPositionInfo.Scaling,
Popup.Child, motion);
motionActor.DispatchInSceneLayer = true;
motionActor.SceneParent = placementToplevel;
motionActor.SceneShowed += (sender, args) =>
{
if (Popup.Host is WindowBase window) {
window.Opacity = 0;
Popup.HideShadowLayer();
}
};
motionActor.Completed += (sender, args) =>
{
base.HideCore(false);
_animating = false;
};
director?.Schedule(motionActor);
}
}
@@ -293,10 +293,6 @@ public class FlyoutHost : Control
return;
}
StopMouseEnterTimer();
if (Flyout is Flyout atomFlyout) {
atomFlyout.RequestCloseWhereAnimationCompleted = true;
}
if (MouseLeaveDelay == 0) {
Flyout.Hide();
+25 -7
View File
@@ -233,6 +233,13 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
IsOpen = false;
Popup.IsOpen = false;
HandlePopupClosed();
return true;
}
protected void HandlePopupClosed()
{
((ISetLogicalParent)Popup).SetParent(null);
// Ensure this isn't active
@@ -249,12 +256,22 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
OnClosed();
Target = null;
return true;
}
/// <returns>True, if action was handled</returns>
protected virtual bool ShowAtCore(Control placementTarget, bool showAtPointer = false)
{
if (!PrepareShowPopup(placementTarget, showAtPointer)) {
return false;
}
IsOpen = Popup.IsOpen = true;
HandlePopupOpened(placementTarget);
return true;
}
protected bool PrepareShowPopup(Control placementTarget, bool showAtPointer = false)
{
if (placementTarget == null) {
throw new ArgumentNullException(nameof(placementTarget));
@@ -290,8 +307,11 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
}
NotifyPositionPopup(showAtPointer);
IsOpen = Popup.IsOpen = true;
return true;
}
protected void HandlePopupOpened(Control placementTarget)
{
OnOpened();
placementTarget.DetachedFromVisualTree += PlacementTarget_DetachedFromVisualTree;
@@ -299,7 +319,7 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
if (ShowMode == FlyoutShowMode.Standard) {
// Try and focus content inside Flyout
if (Popup.Child.Focusable) {
if (Popup.Child!.Focusable) {
Popup.Child.Focus();
} else {
var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next);
@@ -309,8 +329,6 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>();
_transientDisposable = inputManager?.Process.Subscribe(HandleTransientDismiss);
}
return true;
}
private void PlacementTarget_DetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
+13 -19
View File
@@ -333,9 +333,9 @@ public abstract class AbstractPopup : Control, IPopupHostProvider
private bool _isOpenRequested;
private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState;
protected PopupOpenState? _openState;
private Action<IPopupHost?>? _popupHostChangedHandler;
private bool _animating = false;
// 当鼠标移走了,但是打开动画还没完成,我们需要记录下来这个信号
internal bool RequestCloseWhereAnimationCompleted { get; set; } = false;
@@ -379,7 +379,13 @@ public abstract class AbstractPopup : Control, IPopupHostProvider
if (_openState != null) {
return;
}
RequestCloseWhereAnimationCompleted = false;
PrepareOpenState();
OpenOverride();
}
protected void PrepareOpenState()
{
var placementTarget = GetEffectivePlacementTarget();
if (placementTarget == null) {
@@ -502,16 +508,13 @@ public abstract class AbstractPopup : Control, IPopupHostProvider
}
_openState = new PopupOpenState(placementTarget, topLevel, popupHost, cleanupPopup);
WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);
NotifyPopupRootAboutToShow(popupHost);
OpenOverride(popupHost);
}
protected virtual void OpenOverride(IPopupHost popupHost)
protected virtual void OpenOverride()
{
popupHost.Show();
_openState?.PopupHost.Show();
using (BeginIgnoringIsOpen()) {
SetCurrentValue(IsOpenProperty, true);
}
@@ -535,12 +538,7 @@ public abstract class AbstractPopup : Control, IPopupHostProvider
private void CloseCore()
{
if (_animating) {
RequestCloseWhereAnimationCompleted = true;
return;
}
var closingArgs = new CancelEventArgs();
Closing?.Invoke(this, closingArgs);
if (closingArgs.Cancel) {
return;
@@ -553,11 +551,7 @@ public abstract class AbstractPopup : Control, IPopupHostProvider
}
return;
}
CloseOverride();
}
protected virtual void CloseOverride()
{
_openState?.Dispose();
_openState = null;
@@ -906,7 +900,7 @@ public abstract class AbstractPopup : Control, IPopupHostProvider
}
}
private class PopupOpenState : IDisposable
protected class PopupOpenState : IDisposable
{
private readonly IDisposable _cleanup;
private IDisposable? _presenterCleanup;
+122 -127
View File
@@ -1,4 +1,6 @@
using System.Reactive.Disposables;
using AtomUI.Controls.MotionScene;
using AtomUI.MotionScene;
using AtomUI.Styling;
using AtomUI.Utils;
using Avalonia;
@@ -9,6 +11,7 @@ using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Threading;
namespace AtomUI.Controls;
@@ -22,6 +25,10 @@ public class Popup : AbstractPopup
public static readonly StyledProperty<double> MarginToAnchorProperty =
AvaloniaProperty.Register<Popup, double>(nameof(MarginToAnchor));
private static readonly StyledProperty<TimeSpan> MotionDurationProperty
= AvaloniaProperty.Register<Popup, TimeSpan>(nameof(MotionDuration));
public static readonly DirectProperty<Popup, bool> IsFlippedProperty =
AvaloniaProperty.RegisterDirect<Popup, bool>(nameof(IsFlipped),
o => o.IsFlipped,
@@ -39,6 +46,12 @@ public class Popup : AbstractPopup
set => SetValue(MarginToAnchorProperty, value);
}
public TimeSpan MotionDuration
{
get => GetValue(MotionDurationProperty);
set => SetValue(MotionDurationProperty, value);
}
private bool _isFlipped = false;
public bool IsFlipped
{
@@ -50,6 +63,7 @@ public class Popup : AbstractPopup
private CompositeDisposable? _compositeDisposable;
private bool _initialized;
private ManagedPopupPositionerInfo? _managedPopupPositioner;
protected bool _animating = false;
static Popup()
{
@@ -63,11 +77,12 @@ public class Popup : AbstractPopup
IsLightDismissEnabled = false;
}
public sealed override void ApplyTemplate()
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.ApplyTemplate();
base.OnAttachedToLogicalTree(e);
if (!_initialized) {
BindUtils.CreateGlobalTokenBinding(this, MaskShadowsProperty, GlobalResourceKey.BoxShadowsSecondary);
BindUtils.CreateGlobalTokenBinding(this, MotionDurationProperty, GlobalResourceKey.MotionDurationMid);
_initialized = true;
}
}
@@ -204,9 +219,9 @@ public class Popup : AbstractPopup
/// </summary>
/// <param name="popupHost"></param>
/// <param name="placementTarget"></param>
protected internal override void NotifyHostPositionUpdated(IPopupHost popupHost, Control placementTarget)
protected internal override void NotifyAboutToUpdateHostPosition(IPopupHost popupHost, Control placementTarget)
{
base.NotifyHostPositionUpdated(popupHost, placementTarget);
base.NotifyAboutToUpdateHostPosition(popupHost, placementTarget);
var offsetX = HorizontalOffset;
var offsetY = VerticalOffset;
@@ -277,119 +292,6 @@ public class Popup : AbstractPopup
PositionFlipped?.Invoke(this, new PopupFlippedEventArgs(IsFlipped));
}
}
internal static PopupPositionInfo CalculatePositionInfo(Control placementTarget,
double marginToAnchor,
Control popupContent,
Point offset,
PlacementMode placement,
PopupAnchor placementAnchor,
PopupGravity placementGravity,
Rect? placementRect,
FlowDirection flowDirection)
{
var offsetX = offset.X;
var offsetY = offset.Y;
var marginToAnchorOffset = PopupUtils.CalculateMarginToAnchorOffset(placement, marginToAnchor, placementAnchor, placementGravity);
offsetX += marginToAnchorOffset.X;
offsetY += marginToAnchorOffset.Y;
var direction = PopupUtils.GetDirection(placement);
PopupPositionerParameters parameters = new PopupPositionerParameters();
var parentTopLevel = TopLevel.GetTopLevel(placementTarget)!;
// Popup.Child can't be null here, it was set in ShowAtCore.
if (popupContent.DesiredSize == default) {
// Popup may not have been shown yet. Measure content
parameters.Size = LayoutHelper.MeasureChild(popupContent, Size.Infinity, new Thickness());
} else {
parameters.Size = popupContent.DesiredSize;
}
if (placement != PlacementMode.Center &&
placement != PlacementMode.Pointer) {
offset = new Point(offsetX, offsetY);
}
parameters.ConfigurePosition(parentTopLevel,
placementTarget,
placement,
offset,
placementAnchor,
placementGravity,
PopupPositionerConstraintAdjustment.All,
placementRect ?? new Rect(default, placementTarget.Bounds.Size),
flowDirection);
var positionInfo = new PopupPositionInfo();
positionInfo.EffectivePlacement = placement;
positionInfo.EffectivePlacementAnchor = placementAnchor;
positionInfo.EffectivePlacementGravity = placementGravity;
positionInfo.Size = parameters.Size;
positionInfo.Offset = parameters.Offset;
var scaling = parentTopLevel.RenderScaling;
var parentGeometry = GetParentClientAreaScreenGeometry(parentTopLevel);
var screens = GetScreenInfos(parentTopLevel);
if (placement != PlacementMode.Center &&
placement != PlacementMode.Pointer) {
// 计算是否 flip
var anchorRect = new Rect(
parameters.AnchorRectangle.TopLeft * scaling,
parameters.AnchorRectangle.Size * scaling);
var parentOffsetPoint = parentTopLevel.PointToScreen(default);
anchorRect = anchorRect.Translate(new Point(parentOffsetPoint.X, parentOffsetPoint.Y));
var bounds = GetBounds(anchorRect, parentGeometry,screens);
var flipInfo = CalculateFlipInfo(bounds,
parameters.Size * scaling,
anchorRect,
parameters.Anchor,
parameters.Gravity,
offset * scaling);
if (flipInfo.Item1 || flipInfo.Item2) {
var flipPlacement = GetFlipPlacement(placement);
var flipAnchorAndGravity = PopupUtils.GetAnchorAndGravity(flipPlacement);
var flipOffset = PopupUtils.CalculateMarginToAnchorOffset(flipPlacement, marginToAnchor, placementAnchor, placementGravity);
positionInfo.EffectivePlacement = flipPlacement;
positionInfo.EffectivePlacementAnchor = flipAnchorAndGravity.Item1;
positionInfo.EffectivePlacementGravity = flipAnchorAndGravity.Item2;
if (direction == Direction.Top || direction == Direction.Bottom) {
positionInfo.Offset = positionInfo.Offset.WithY(flipOffset.Y);
} else {
positionInfo.Offset = positionInfo.Offset.WithX(flipOffset.X);
}
positionInfo.IsFlipped = true;
} else {
positionInfo.IsFlipped = false;
}
}
var rect = PopupUtils.Calculate(
parameters.Size * scaling,
new Rect(
parameters.AnchorRectangle.TopLeft * scaling,
parameters.AnchorRectangle.Size * scaling),
parameters.Anchor,
parameters.Gravity,
parameters.ConstraintAdjustment,
positionInfo.Offset * scaling,
parentGeometry,
screens);
positionInfo.Offset = new Point(Math.Round(rect.Position.X), Math.Floor(rect.Position.Y + 0.5));
positionInfo.Size = rect.Size;
positionInfo.Scaling = scaling;
return positionInfo;
}
protected static PlacementMode GetFlipPlacement(PlacementMode placement)
{
@@ -421,17 +323,110 @@ public class Popup : AbstractPopup
_shadowLayer.Opacity = 0;
}
}
}
// TODO Review 需要评估这里等待的变成模式是否正确高效
public async Task OpenAnimationAsync()
{
// AbstractPopup is currently open
if (_openState != null || _animating) {
return;
}
internal class PopupPositionInfo
{
public Point Offset { get; set; }
public bool IsFlipped { get; set; }
public Size Size { get; set; }
public double Scaling { get; set; }
public PlacementMode EffectivePlacement { get; set; }
public PopupAnchor EffectivePlacementAnchor { get; set; }
public PopupGravity EffectivePlacementGravity { get; set; }
_animating = true;
PrepareOpenState();
if (_openState == null) {
return;
}
// 获取 popup 的具体位置,这个就是非常准确的位置,还有大小
// TODO 暂时只支持 WindowBase popup
var popupRoot = (_openState.PopupHost as PopupRoot)!;
popupRoot.Show();
popupRoot.Hide();
var popupOffset = popupRoot.PlatformImpl!.Position;
var offset = new Point(popupOffset.X, popupOffset.Y);
var topLevel = _openState.TopLevel;
var scaling = topLevel.RenderScaling;
// 调度动画
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;
var cts = new CancellationTokenSource();
var cancelToken = cts.Token;
motionActor.Completed += (sender, args) =>
{
OpenOverride();
if (_openState?.PopupHost is WindowBase window) {
window.PlatformImpl!.SetTopmost(true);
}
_animating = false;
cts.Cancel();
if (RequestCloseWhereAnimationCompleted) {
RequestCloseWhereAnimationCompleted = false;
Dispatcher.UIThread.InvokeAsync(async () => { await CloseAnimationAsync(); });
}
};
director?.Schedule(motionActor);
while (!cancelToken.IsCancellationRequested) {
await Task.Delay(TimeSpan.FromMilliseconds(10), cancelToken);
}
}
public async Task CloseAnimationAsync()
{
if (_animating) {
RequestCloseWhereAnimationCompleted = true;
return;
}
if (_openState is null) {
return;
}
_animating = true;
var director = Director.Instance;
var motion = new ZoomBigOutMotion();
motion.ConfigureOpacity(MotionDuration);
motion.ConfigureRenderTransform(MotionDuration);
var popupRoot = (_openState.PopupHost as PopupRoot)!;
var popupOffset = popupRoot.PlatformImpl!.Position;
var offset = new Point(popupOffset.X, popupOffset.Y);
var scaling = _openState.TopLevel.RenderScaling;
var motionActor = new PopupMotionActor(MaskShadows, offset, scaling, Child ?? popupRoot, motion);
motionActor.DispatchInSceneLayer = true;
motionActor.SceneParent = _openState.TopLevel;
var cts = new CancellationTokenSource();
var cancelToken = cts.Token;
motionActor.SceneShowed += (sender, args) =>
{
popupRoot.Opacity = 0;
HideShadowLayer();
};
motionActor.Completed += (sender, args) =>
{
_animating = false;
Close();
cts.Cancel();
};
director?.Schedule(motionActor);
while (!cancelToken.IsCancellationRequested) {
await Task.Delay(TimeSpan.FromMilliseconds(10), cancelToken);
}
}
}
internal class ManagedPopupPositionerInfo
+5 -3
View File
@@ -28,7 +28,8 @@ public sealed class PopupRoot : WindowBase, IHostedVisualTreeRoot, IDisposable,
public static readonly StyledProperty<bool> WindowManagerAddShadowHintProperty =
Popup.WindowManagerAddShadowHintProperty.AddOwner<PopupRoot>();
private PopupPositionerParameters _positionerParameters;
private PopupPositionerParameters _positionerParameters;
internal PopupPositionerParameters PositionerParameters => _positionerParameters;
/// <summary>
/// Initializes static members of the <see cref="PopupRoot"/> class.
@@ -90,7 +91,7 @@ public sealed class PopupRoot : WindowBase, IHostedVisualTreeRoot, IDisposable,
/// <remarks>
/// Popup events are passed to their parent window. This facilitates this.
/// </remarks>
/// TODO 需要评估影响
/// TODO 需要评估影响,可能有严重错误!!!
//internal override Interactive? InteractiveParent => (Interactive?)Parent;
/// <summary>
@@ -139,8 +140,9 @@ public sealed class PopupRoot : WindowBase, IHostedVisualTreeRoot, IDisposable,
_positionerParameters.ConfigurePosition(ParentTopLevel, target,
placement, offset, anchor, gravity, constraintAdjustment, rect, FlowDirection);
if (_positionerParameters.Size != default)
if (_positionerParameters.Size != default) {
UpdatePosition();
}
}
public void SetChild(Control? control) => Content = control;
@@ -41,6 +41,7 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
protected Canvas? _layout;
protected Control _motionTarget;
protected RenderTargetBitmap? _ghostBitmap;
protected RenderTargetBitmap? _contentBitmap;
protected Size _motionTargetSize;
protected Point _maskOffset;
protected Size _maskSize;
@@ -106,11 +107,17 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
foreach (var renderer in shadowRenderers) {
_layout.Children.Add(renderer);
}
var border = new Border()
{
Width = _motionTarget.DesiredSize.Width,
Height = _motionTarget.DesiredSize.Height,
};
_layout.Children.Add(_motionTarget);
_layout.Children.Add(border);
Canvas.SetLeft(_motionTarget, offsetX);
Canvas.SetTop(_motionTarget, offsetY);
Canvas.SetLeft(border, offsetX);
Canvas.SetTop(border, offsetY);
_initialized = true;
}
@@ -162,11 +169,15 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
public override void Render(DrawingContext context)
{
if (_ghostBitmap is not null) {
var scaling = TopLevel.GetTopLevel(this)?.RenderScaling ?? 1.0;
var scaling = TopLevel.GetTopLevel(this)?.RenderScaling ?? 1.0;
if (_ghostBitmap is not null && _contentBitmap is not null) {
context.DrawImage(_ghostBitmap, new Rect(new Point(0, 0), DesiredSize * scaling),
new Rect(new Point(0, 0), DesiredSize));
var shadowThickness = Shadows.Thickness();
var offsetX = shadowThickness.Left;
var offsetY = shadowThickness.Top;
context.DrawImage(_contentBitmap, new Rect(new Point(0, 0), _motionTargetSize * scaling),
new Rect(new Point(offsetX, offsetY), _motionTargetSize));
}
}
@@ -176,7 +187,10 @@ internal class MotionGhostControl : Control, INotifyCaptureGhostBitmap
var scaling = TopLevel.GetTopLevel(this)?.RenderScaling ?? 1.0;
_ghostBitmap = new RenderTargetBitmap(new PixelSize((int)(DesiredSize.Width * scaling), (int)(DesiredSize.Height * scaling)),
new Vector(96 * scaling, 96 * scaling));
_contentBitmap = new RenderTargetBitmap(new PixelSize((int)(_motionTargetSize.Width * scaling), (int)(_motionTargetSize.Height * scaling)),
new Vector(96 * scaling, 96 * scaling));
_ghostBitmap.Render(this);
_contentBitmap.Render(_motionTarget);
_layout!.Children.Clear();
}
}
+11 -127
View File
@@ -186,9 +186,6 @@ public class ToolTip : TemplatedControl,
}
#endregion
// 当鼠标移走了,但是打开动画还没完成,我们需要记录下来这个信号
internal bool RequestCloseWhereAnimationCompleted { get; set; } = false;
static ToolTip()
{
@@ -496,10 +493,8 @@ public class ToolTip : TemplatedControl,
private Popup? _popup;
private Action<IPopupHost?>? _popupHostChangedHandler;
private PopupPositionInfo? _popupPositionInfo; // 这个信息在隐藏动画的时候会用到
private IControlCustomStyle _customStyle;
private ArrowDecoratedBox? _arrowDecoratedBox;
private bool _animating;
internal Control? AdornedControl { get; private set; }
internal event EventHandler? Closed;
private static StyledProperty<ThemeVariant?> RequestedThemeVariantProperty;
@@ -602,12 +597,7 @@ public class ToolTip : TemplatedControl,
private void Open(Control control)
{
Close();
if (_animating) {
return;
}
RequestCloseWhereAnimationCompleted = false;
_animating = true;
if (_popup is null) {
_popup = new Popup();
_popup.Child = this;
@@ -629,7 +619,6 @@ public class ToolTip : TemplatedControl,
}
var placement = GetPlacement(control);
var isPointAtCenter = GetIsPointAtCenter(control);
var offsetX = GetHorizontalOffset(control);
var offsetY = GetVerticalOffset(control);
@@ -649,80 +638,16 @@ public class ToolTip : TemplatedControl,
// 后期看能不能检测对应字段的改变
CalculateShowArrowEffective(control);
// 计算动画相关的数据
_popupPositionInfo = Popup.CalculatePositionInfo(control,
marginToAnchor,
this,
new Point(offsetX, offsetY),
placement,
anchorAndGravity.Item1,
anchorAndGravity.Item2,
null,
_popup.FlowDirection);
// 重新设置箭头位置
// 因为可能有 flip 的情况
var arrowPosition = PopupUtils.CalculateArrowPosition(_popupPositionInfo.EffectivePlacement,
_popupPositionInfo.EffectivePlacementAnchor,
_popupPositionInfo.EffectivePlacementGravity);
if (arrowPosition.HasValue) {
_arrowDecoratedBox!.ArrowPosition = arrowPosition.Value;
}
// 获取是否在指向中点
var pointAtCenterOffset = CalculatePopupPositionDelta(control,
_popupPositionInfo.EffectivePlacement,
_popupPositionInfo.EffectivePlacementAnchor,
_popupPositionInfo.EffectivePlacementGravity);
if (isPointAtCenter) {
_popupPositionInfo.Offset = new Point(
Math.Floor(_popupPositionInfo.Offset.X + pointAtCenterOffset.X * _popupPositionInfo.Scaling),
Math.Floor(_popupPositionInfo.Offset.Y + pointAtCenterOffset.Y * _popupPositionInfo.Scaling));
}
_popup.MarginToAnchor = marginToAnchor;
_popup.Placement = placement;
_popup.PlacementTarget = control;
// 开始动画
PlayShowMotion(_popupPositionInfo, control, this);
}
private void PlayShowMotion(PopupPositionInfo positionInfo, Control placementTarget, Control contentControl)
{
var director = Director.Instance;
var motion = new ZoomBigInMotion();
motion.ConfigureOpacity(MotionDuration);
motion.ConfigureRenderTransform(MotionDuration);
var topLevel = TopLevel.GetTopLevel(placementTarget);
var motionActor =
new PopupMotionActor(Shadows, positionInfo.Offset, positionInfo.Scaling, contentControl, motion);
motionActor.DispatchInSceneLayer = true;
motionActor.SceneParent = topLevel;
motionActor.Completed += (sender, args) =>
Dispatcher.UIThread.InvokeAsync(async () =>
{
if (_popup is not null) {
_popup.IsOpen = true;
if (_popup?.Host is WindowBase window) {
window.PlatformImpl!.SetTopmost(true);
}
}
_animating = false;
if (RequestCloseWhereAnimationCompleted) {
Dispatcher.UIThread.Post(() =>
{
if (_popup is not null) {
_popup.IsOpen = false;
}
});
}
};
director?.Schedule(motionActor);
await _popup.OpenAnimationAsync();
});
}
private bool CalculateShowArrowEffective(Control control)
{
if (GetIsShowArrow(control) == false) {
@@ -735,57 +660,16 @@ public class ToolTip : TemplatedControl,
private void Close()
{
if (_popup is null || !_popup.IsOpen || _animating) {
RequestCloseWhereAnimationCompleted = true;
if (_popup is null) {
return;
}
PlayHideMotion(_popup);
Dispatcher.UIThread.InvokeAsync(async () =>
{
await _popup.CloseAnimationAsync();
});
}
private void PlayHideMotion(Popup popup)
{
var placementToplevel = TopLevel.GetTopLevel(popup.PlacementTarget);
if (_popupPositionInfo is null ||
popup.Child is null ||
placementToplevel is null) {
// 没有动画位置信息,直接关闭
popup.IsOpen = false;
return;
}
_animating = true;
var director = Director.Instance;
var motion = new ZoomBigOutMotion();
motion.ConfigureOpacity(MotionDuration);
motion.ConfigureRenderTransform(MotionDuration);
UIStructureUtils.SetVisualParent(popup.Child, null);
UIStructureUtils.SetVisualParent(popup.Child, null);
var motionActor = new PopupMotionActor(Shadows, _popupPositionInfo.Offset, _popupPositionInfo.Scaling,
popup.Child, motion);
motionActor.DispatchInSceneLayer = true;
motionActor.SceneParent = placementToplevel;
motionActor.SceneShowed += (sender, args) =>
{
if (popup.Host is WindowBase window) {
window.Opacity = 0;
popup.HideShadowLayer();
}
};
motionActor.Completed += (sender, args) =>
{
popup.IsOpen = false;
SetPopupParent(popup, null);
popup.PlacementTarget = null;
_animating = false;
};
director?.Schedule(motionActor);
}
private void OnPopupPositionFlipped(object? sender, PopupFlippedEventArgs e)
{
if (sender is Popup popup) {
-1
View File
@@ -58,7 +58,6 @@ public class Director : IDirector
sceneLayer.Topmost = true;
actor.NotifySceneShowed();
}
HandleMotionPreStart(actor);
ExecuteMotionAction(actor);
HandleMotionStarted(actor);