mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 23:08:36 +08:00
优化popup 动画
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -58,7 +58,6 @@ public class Director : IDirector
|
||||
sceneLayer.Topmost = true;
|
||||
actor.NotifySceneShowed();
|
||||
}
|
||||
|
||||
HandleMotionPreStart(actor);
|
||||
ExecuteMotionAction(actor);
|
||||
HandleMotionStarted(actor);
|
||||
|
||||
Reference in New Issue
Block a user