From 0448c014acc8aee5871995c37a6747ce2520813f Mon Sep 17 00:00:00 2001 From: polarboy Date: Wed, 24 Jul 2024 20:27:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96popup=20=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AtomUI.Controls/Flyouts/Flyout.cs | 187 +++---------- src/AtomUI.Controls/Flyouts/FlyoutHost.cs | 4 - .../Flyouts/PopupFlyoutBase.cs | 32 ++- src/AtomUI.Controls/Popup/AbstractPopup.cs | 32 +-- src/AtomUI.Controls/Popup/Popup.cs | 249 +++++++++--------- src/AtomUI.Controls/Popup/PopupRoot.cs | 8 +- .../Primitives/MotionGhostControl.cs | 26 +- src/AtomUI.Controls/Tooltip/ToolTip.cs | 138 +--------- src/AtomUI/MotionScene/Director.cs | 1 - 9 files changed, 232 insertions(+), 445 deletions(-) diff --git a/src/AtomUI.Controls/Flyouts/Flyout.cs b/src/AtomUI.Controls/Flyouts/Flyout.cs index 825e03479..96369019b 100644 --- a/src/AtomUI.Controls/Flyouts/Flyout.cs +++ b/src/AtomUI.Controls/Flyouts/Flyout.cs @@ -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); - } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Flyouts/FlyoutHost.cs b/src/AtomUI.Controls/Flyouts/FlyoutHost.cs index ece768a31..e0b415cc0 100644 --- a/src/AtomUI.Controls/Flyouts/FlyoutHost.cs +++ b/src/AtomUI.Controls/Flyouts/FlyoutHost.cs @@ -293,10 +293,6 @@ public class FlyoutHost : Control return; } StopMouseEnterTimer(); - - if (Flyout is Flyout atomFlyout) { - atomFlyout.RequestCloseWhereAnimationCompleted = true; - } if (MouseLeaveDelay == 0) { Flyout.Hide(); diff --git a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs index 4e638331c..37c978f3e 100644 --- a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs @@ -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; } /// True, if action was handled 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(); _transientDisposable = inputManager?.Process.Subscribe(HandleTransientDismiss); } - - return true; } private void PlacementTarget_DetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) diff --git a/src/AtomUI.Controls/Popup/AbstractPopup.cs b/src/AtomUI.Controls/Popup/AbstractPopup.cs index 60fb7a35c..da5a2e1cf 100644 --- a/src/AtomUI.Controls/Popup/AbstractPopup.cs +++ b/src/AtomUI.Controls/Popup/AbstractPopup.cs @@ -333,9 +333,9 @@ public abstract class AbstractPopup : Control, IPopupHostProvider private bool _isOpenRequested; private bool _ignoreIsOpenChanged; - private PopupOpenState? _openState; + protected PopupOpenState? _openState; private Action? _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; diff --git a/src/AtomUI.Controls/Popup/Popup.cs b/src/AtomUI.Controls/Popup/Popup.cs index 7dd201c35..6ea29cb3d 100644 --- a/src/AtomUI.Controls/Popup/Popup.cs +++ b/src/AtomUI.Controls/Popup/Popup.cs @@ -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 MarginToAnchorProperty = AvaloniaProperty.Register(nameof(MarginToAnchor)); + + private static readonly StyledProperty MotionDurationProperty + = AvaloniaProperty.Register(nameof(MotionDuration)); + public static readonly DirectProperty IsFlippedProperty = AvaloniaProperty.RegisterDirect(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 /// /// /// - 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 diff --git a/src/AtomUI.Controls/Popup/PopupRoot.cs b/src/AtomUI.Controls/Popup/PopupRoot.cs index 0fc00d965..4a586ff40 100644 --- a/src/AtomUI.Controls/Popup/PopupRoot.cs +++ b/src/AtomUI.Controls/Popup/PopupRoot.cs @@ -28,7 +28,8 @@ public sealed class PopupRoot : WindowBase, IHostedVisualTreeRoot, IDisposable, public static readonly StyledProperty WindowManagerAddShadowHintProperty = Popup.WindowManagerAddShadowHintProperty.AddOwner(); - private PopupPositionerParameters _positionerParameters; + private PopupPositionerParameters _positionerParameters; + internal PopupPositionerParameters PositionerParameters => _positionerParameters; /// /// Initializes static members of the class. @@ -90,7 +91,7 @@ public sealed class PopupRoot : WindowBase, IHostedVisualTreeRoot, IDisposable, /// /// Popup events are passed to their parent window. This facilitates this. /// - /// TODO 需要评估影响 + /// TODO 需要评估影响,可能有严重错误!!! //internal override Interactive? InteractiveParent => (Interactive?)Parent; /// @@ -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; diff --git a/src/AtomUI.Controls/Primitives/MotionGhostControl.cs b/src/AtomUI.Controls/Primitives/MotionGhostControl.cs index 2b119a4d2..b6f8fe5cb 100644 --- a/src/AtomUI.Controls/Primitives/MotionGhostControl.cs +++ b/src/AtomUI.Controls/Primitives/MotionGhostControl.cs @@ -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(); } } diff --git a/src/AtomUI.Controls/Tooltip/ToolTip.cs b/src/AtomUI.Controls/Tooltip/ToolTip.cs index 0ceb4c6ac..525646f80 100644 --- a/src/AtomUI.Controls/Tooltip/ToolTip.cs +++ b/src/AtomUI.Controls/Tooltip/ToolTip.cs @@ -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? _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 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) { diff --git a/src/AtomUI/MotionScene/Director.cs b/src/AtomUI/MotionScene/Director.cs index 31086cf06..ea2b82d49 100644 --- a/src/AtomUI/MotionScene/Director.cs +++ b/src/AtomUI/MotionScene/Director.cs @@ -58,7 +58,6 @@ public class Director : IDirector sceneLayer.Topmost = true; actor.NotifySceneShowed(); } - HandleMotionPreStart(actor); ExecuteMotionAction(actor); HandleMotionStarted(actor);