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);