diff --git a/src/AtomUI.Base/MotionScene/AbstractMotion.cs b/src/AtomUI.Base/MotionScene/AbstractMotion.cs index 7970747fd..9de4c0fd8 100644 --- a/src/AtomUI.Base/MotionScene/AbstractMotion.cs +++ b/src/AtomUI.Base/MotionScene/AbstractMotion.cs @@ -9,8 +9,6 @@ namespace AtomUI.MotionScene; public class AbstractMotion : IMotion { - public bool IsRunning { get; } - public RelativePoint RenderTransformOrigin { get; protected set; } public IList Animations { get; } public TimeSpan Duration { get; } @@ -41,12 +39,13 @@ public class AbstractMotion : IMotion { aboutToStart(); } + actor.IsVisible = true; foreach (var animation in Animations) { await animation.RunAsync(actor, cancellationToken); } - + actor.NotifyMotionCompleted(); NotifyCompleted(); if (completedAction is not null) diff --git a/src/AtomUI.Base/MotionScene/IMotion.cs b/src/AtomUI.Base/MotionScene/IMotion.cs index 4bf0227c2..863138965 100644 --- a/src/AtomUI.Base/MotionScene/IMotion.cs +++ b/src/AtomUI.Base/MotionScene/IMotion.cs @@ -6,7 +6,6 @@ namespace AtomUI.MotionScene; internal interface IMotion { - public bool IsRunning { get; } public RelativePoint RenderTransformOrigin { get; } public IList Animations { get; } public TimeSpan Duration { get; } diff --git a/src/AtomUI.Base/MotionScene/MotionInvoker.cs b/src/AtomUI.Base/MotionScene/MotionInvoker.cs index b88f72f24..2497379ce 100644 --- a/src/AtomUI.Base/MotionScene/MotionInvoker.cs +++ b/src/AtomUI.Base/MotionScene/MotionInvoker.cs @@ -16,21 +16,21 @@ internal static class MotionInvoker await motion.RunAsync(actor, aboutToStart, completedAction, cancellationToken); }); } - + public static async Task InvokeAsync(MotionActorControl actor, - AbstractMotion motion, - Action? aboutToStart = null, - Action? completedAction = null, - CancellationToken cancellationToken = default) + AbstractMotion motion, + Action? aboutToStart = null, + Action? completedAction = null, + CancellationToken cancellationToken = default) { await motion.RunAsync(actor, aboutToStart, completedAction, cancellationToken); } - + public static void InvokeInPopupLayer(SceneMotionActorControl actor, - AbstractMotion motion, - Action? aboutToStart = null, - Action? completedAction = null, - CancellationToken cancellationToken = default) + AbstractMotion motion, + Action? aboutToStart = null, + Action? completedAction = null, + CancellationToken cancellationToken = default) { Dispatcher.UIThread.Invoke(async () => { @@ -39,12 +39,12 @@ internal static class MotionInvoker } public static async Task InvokeInPopupLayerAsync(SceneMotionActorControl actor, - AbstractMotion motion, - Action? aboutToStart = null, - Action? completedAction = null, - CancellationToken cancellationToken = default) + AbstractMotion motion, + Action? aboutToStart = null, + Action? completedAction = null, + CancellationToken cancellationToken = default) { - var sceneLayer = PrepareSceneLayer(motion, actor); + var sceneLayer = PrepareSceneLayer(motion, actor); var compositeDisposable = new CompositeDisposable(); compositeDisposable.Add(Disposable.Create(sceneLayer, (state) => { diff --git a/src/AtomUI.Base/Reflection/ObjectExtension.cs b/src/AtomUI.Base/Reflection/ObjectExtension.cs index bbaae1be1..0df06ecfc 100644 --- a/src/AtomUI.Base/Reflection/ObjectExtension.cs +++ b/src/AtomUI.Base/Reflection/ObjectExtension.cs @@ -53,6 +53,16 @@ public static class ObjectExtension return false; } + public static T? GetPropertyOrThrow(this object source, PropertyInfo info) + { + var obj = info.GetValue(source); + if (obj is T propertyOrThrow) + { + return propertyOrThrow; + } + throw new Exception($"Get Property {info.Name} value failed."); + } + public static T? GetPropertyOrThrow(this object source, string name, BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy) diff --git a/src/AtomUI.Base/Utils/AvaloniaPropertyReflectionExtensions.cs b/src/AtomUI.Base/Utils/AvaloniaPropertyReflectionExtensions.cs new file mode 100644 index 000000000..3cbd3591d --- /dev/null +++ b/src/AtomUI.Base/Utils/AvaloniaPropertyReflectionExtensions.cs @@ -0,0 +1,14 @@ +using AtomUI.Reflection; +using Avalonia; + +namespace AtomUI.Utils; + +// 反射扩展类 +internal static class AvaloniaPropertyReflectionExtensions +{ + public static void NotifyChanged(this AvaloniaProperty property, AvaloniaPropertyChangedEventArgs e) + { + var methodInfo = typeof(AvaloniaProperty).GetMethodInfoOrThrow("NotifyChanged"); + methodInfo.Invoke(property, [e]); + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs index ae7aff8a3..f22ed6e69 100644 --- a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs +++ b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs @@ -1,6 +1,4 @@ -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.CompilerServices; +using System.Diagnostics; using AtomUI.Theme; using AtomUI.Theme.Utils; using Avalonia; @@ -176,6 +174,7 @@ public class ArrowDecoratedBox : ContentControl, { AffectsMeasure(IsShowArrowProperty, ArrowDirectionProperty); AffectsArrange(ArrowPositionProperty); + AffectsRender(BackgroundProperty); } public ArrowDecoratedBox() @@ -261,7 +260,7 @@ public class ArrowDecoratedBox : ContentControl, } return targetRect; } - + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -426,5 +425,4 @@ public class ArrowDecoratedBox : ContentControl, _arrowPlacementFlipped = false; } - } \ No newline at end of file diff --git a/src/AtomUI.Controls/Flyouts/Flyout.cs b/src/AtomUI.Controls/Flyouts/Flyout.cs index 524ddb562..7e19bc371 100644 --- a/src/AtomUI.Controls/Flyouts/Flyout.cs +++ b/src/AtomUI.Controls/Flyouts/Flyout.cs @@ -154,6 +154,7 @@ public class Flyout : PopupFlyoutBase var anchor = popup.PlacementAnchor; var gravity = popup.PlacementGravity; + // TODO 可以改进成绑定,可以参考 Tooltip 的做法 if (flyoutPresenter is not null) { var arrowPosition = PopupUtils.CalculateArrowPosition(placement, anchor, gravity); @@ -345,12 +346,10 @@ public class Flyout : PopupFlyoutBase } IsOpen = true; - Dispatcher.UIThread.InvokeAsync(() => + Dispatcher.UIThread.InvokeAsync(async () => { - Popup.OpenAnimation(() => - { - HandlePopupOpened(placementTarget); - }); + await Popup.OpenAsync(); + HandlePopupOpened(placementTarget); }); return true; } @@ -376,7 +375,11 @@ public class Flyout : PopupFlyoutBase } IsOpen = false; - Dispatcher.UIThread.InvokeAsync(() => { Popup.CloseAnimation(HandlePopupClosed); }); + Dispatcher.UIThread.InvokeAsync(async () => + { + await Popup.CloseAsync(); + HandlePopupClosed(); + }); return true; } diff --git a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs index afcea8057..a16104b53 100644 --- a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs @@ -19,6 +19,7 @@ namespace AtomUI.Controls; public class FlyoutPresenterCreatedEventArgs : EventArgs { public Control Presenter { get; } + public FlyoutPresenterCreatedEventArgs(Control presenter) { Presenter = presenter; @@ -33,60 +34,60 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider #region 公共属性定义 /// - /// 距离 anchor 的边距,根据垂直和水平进行设置 - /// 但是对某些组合无效,比如跟随鼠标的情况 - /// 还有些 anchor 和 gravity 的组合也没有用 - /// - public static readonly StyledProperty MarginToAnchorProperty = + /// 距离 anchor 的边距,根据垂直和水平进行设置 + /// 但是对某些组合无效,比如跟随鼠标的情况 + /// 还有些 anchor 和 gravity 的组合也没有用 + /// + public static readonly StyledProperty MarginToAnchorProperty = Popup.MarginToAnchorProperty.AddOwner(); - /// - public static readonly StyledProperty PlacementProperty = + /// + public static readonly StyledProperty PlacementProperty = Avalonia.Controls.Primitives.Popup.PlacementProperty.AddOwner(); - /// - public static readonly StyledProperty HorizontalOffsetProperty = + /// + public static readonly StyledProperty HorizontalOffsetProperty = Avalonia.Controls.Primitives.Popup.HorizontalOffsetProperty.AddOwner(); - /// - public static readonly StyledProperty VerticalOffsetProperty = + /// + public static readonly StyledProperty VerticalOffsetProperty = Avalonia.Controls.Primitives.Popup.VerticalOffsetProperty.AddOwner(); - /// - public static readonly StyledProperty PlacementAnchorProperty = + /// + public static readonly StyledProperty PlacementAnchorProperty = Avalonia.Controls.Primitives.Popup.PlacementAnchorProperty.AddOwner(); - /// - public static readonly StyledProperty PlacementGravityProperty = + /// + public static readonly StyledProperty PlacementGravityProperty = Avalonia.Controls.Primitives.Popup.PlacementGravityProperty.AddOwner(); - /// - /// Defines the property - /// - public static readonly StyledProperty ShowModeProperty = + /// + /// Defines the property + /// + public static readonly StyledProperty ShowModeProperty = AvaloniaProperty.Register(nameof(ShowMode)); - /// - /// Defines the property - /// - public static readonly StyledProperty OverlayInputPassThroughElementProperty = + /// + /// Defines the property + /// + public static readonly StyledProperty OverlayInputPassThroughElementProperty = Avalonia.Controls.Primitives.Popup.OverlayInputPassThroughElementProperty.AddOwner(); - /// - /// Defines the property - /// - public static readonly StyledProperty PlacementConstraintAdjustmentProperty = + /// + /// Defines the property + /// + public static readonly StyledProperty PlacementConstraintAdjustmentProperty = Avalonia.Controls.Primitives.Popup.PlacementConstraintAdjustmentProperty.AddOwner(); - - public static readonly StyledProperty IsMotionEnabledProperty - = AvaloniaProperty.Register(nameof(IsMotionEnabled)); + + public static readonly StyledProperty IsMotionEnabledProperty + = AvaloniaProperty.Register(nameof(IsMotionEnabled)); public double MarginToAnchor { get => GetValue(MarginToAnchorProperty); set => SetValue(MarginToAnchorProperty, value); } - + /// public PlacementMode Placement { @@ -147,7 +148,7 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider get => GetValue(PlacementConstraintAdjustmentProperty); set => SetValue(PlacementConstraintAdjustmentProperty, value); } - + public bool IsMotionEnabled { get => GetValue(IsMotionEnabledProperty); @@ -160,7 +161,7 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider internal static readonly StyledProperty IsDetectMouseClickEnabledProperty = AvaloniaProperty.Register(nameof(IsDetectMouseClickEnabled), true); - + internal bool IsDetectMouseClickEnabled { get => GetValue(IsDetectMouseClickEnabledProperty); @@ -284,6 +285,7 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider return false; } } + IsOpen = false; Popup.IsOpen = false; @@ -304,8 +306,8 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider if (Target != null) { - Target.DetachedFromVisualTree -= PlacementTarget_DetachedFromVisualTree; - Target.KeyUp -= OnPlacementTargetOrPopupKeyUp; + Target.DetachedFromVisualTree -= HandlePlacementTargetDetachedFromVisualTree; + Target.KeyUp -= HandlePlacementTargetOrPopupKeyUp; } OnClosed(); @@ -377,8 +379,8 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider { OnOpened(); - placementTarget.DetachedFromVisualTree += PlacementTarget_DetachedFromVisualTree; - placementTarget.KeyUp += OnPlacementTargetOrPopupKeyUp; + placementTarget.DetachedFromVisualTree += HandlePlacementTargetDetachedFromVisualTree; + placementTarget.KeyUp += HandlePlacementTargetOrPopupKeyUp; if (ShowMode == FlyoutShowMode.Standard) { @@ -400,7 +402,7 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider } } - private void PlacementTarget_DetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) + private void HandlePlacementTargetDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) { _ = HideCore(false); } @@ -494,9 +496,9 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider // popup.Closing += OnPopupClosing; var handler = new EventHandler(OnPopupClosing); var closingEventAddMethod = ClosingEventInfo.GetAddMethod(true); - closingEventAddMethod?.Invoke(popup, new object?[] { handler }); + closingEventAddMethod?.Invoke(popup, [handler]); - popup.KeyUp += OnPlacementTargetOrPopupKeyUp; + popup.KeyUp += HandlePlacementTargetOrPopupKeyUp; NotifyPopupCreated(popup); return popup; } @@ -524,7 +526,7 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider } // This method is handling both popup logical tree and target logical tree. - private void OnPlacementTargetOrPopupKeyUp(object? sender, KeyEventArgs e) + private void HandlePlacementTargetOrPopupKeyUp(object? sender, KeyEventArgs e) { if (!e.Handled && IsOpen diff --git a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs index ee7b088e7..f8731d120 100644 --- a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs +++ b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs @@ -708,7 +708,6 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey ToolTipPadding = new TokenResourceKey("ToolTip.ToolTipPadding"); public static readonly TokenResourceKey ToolTipShadows = new TokenResourceKey("ToolTip.ToolTipShadows"); public static readonly TokenResourceKey ToolTipMotionDuration = new TokenResourceKey("ToolTip.ToolTipMotionDuration"); - public static readonly TokenResourceKey MarginToAnchor = new TokenResourceKey("ToolTip.MarginToAnchor"); public static readonly TokenResourceKey ToolTipArrowSize = new TokenResourceKey("ToolTip.ToolTipArrowSize"); } diff --git a/src/AtomUI.Controls/Popup/Popup.cs b/src/AtomUI.Controls/Popup/Popup.cs index 436431c59..5c306eaa0 100644 --- a/src/AtomUI.Controls/Popup/Popup.cs +++ b/src/AtomUI.Controls/Popup/Popup.cs @@ -3,7 +3,6 @@ using AtomUI.Controls.Primitives; using AtomUI.Data; using AtomUI.Media; using AtomUI.MotionScene; -using AtomUI.Reflection; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; @@ -109,7 +108,7 @@ public class Popup : AvaloniaPopup, private IDisposable? _selfLightDismissDisposable; - private IManagedPopupPositionerPopup? _managedPopupPositionerX; + private IManagedPopupPositionerPopup? _managedPopupPositioner; private bool _isNeedDetectFlip = true; // 在翻转之后或者恢复正常,会有属性的变动,在变动之后捕捉动画需要等一个事件循环,保证布局已经生效 private bool _isNeedWaitFlipSync = false; @@ -160,8 +159,9 @@ public class Popup : AvaloniaPopup, HorizontalOffset = offsetX; VerticalOffset = offsetY; - + _selfLightDismissDisposable?.Dispose(); + _shadowLayer?.Close(); _firstDetected = true; } @@ -172,11 +172,7 @@ public class Popup : AvaloniaPopup, var popupPositioner = popupRoot.PlatformImpl?.PopupPositioner; if (popupPositioner is ManagedPopupPositioner managedPopupPositioner) { - if (managedPopupPositioner.TryGetField("_popup", - out IManagedPopupPositionerPopup? managedPopupPositionerPopup)) - { - _managedPopupPositionerX = managedPopupPositionerPopup; - } + _managedPopupPositioner = managedPopupPositioner.GetManagedPopupPositionerPopup(); } } @@ -230,7 +226,10 @@ public class Popup : AvaloniaPopup, { if (popupHostProvider.PopupHost != pointerEventArgs.Root) { - CloseAnimation(); + Dispatcher.UIThread.InvokeAsync(async () => + { + await CloseAsync(); + }); } } } @@ -303,13 +302,13 @@ public class Popup : AvaloniaPopup, private Rect GetBounds(Rect anchorRect) { // 暂时只支持窗口的方式 - if (_managedPopupPositionerX is null) + if (_managedPopupPositioner is null) { throw new InvalidOperationException("ManagedPopupPositioner is null"); } - var parentGeometry = _managedPopupPositionerX.ParentClientAreaScreenGeometry; - var screens = _managedPopupPositionerX.Screens; + var parentGeometry = _managedPopupPositioner.ParentClientAreaScreenGeometry; + var screens = _managedPopupPositioner.Screens; return GetBounds(anchorRect, parentGeometry, screens); } @@ -376,12 +375,12 @@ public class Popup : AvaloniaPopup, popupSize = Child.DesiredSize; } - Debug.Assert(_managedPopupPositionerX != null); - var scaling = _managedPopupPositionerX.Scaling; + Debug.Assert(_managedPopupPositioner != null); + var scaling = _managedPopupPositioner.Scaling; var anchorRect = new Rect( parameters.AnchorRectangle.TopLeft * scaling, parameters.AnchorRectangle.Size * scaling); - anchorRect = anchorRect.Translate(_managedPopupPositionerX.ParentClientAreaScreenGeometry.TopLeft); + anchorRect = anchorRect.Translate(_managedPopupPositioner.ParentClientAreaScreenGeometry.TopLeft); var flipInfo = CalculateFlipInfo(popupSize * scaling, anchorRect, @@ -465,23 +464,24 @@ public class Popup : AvaloniaPopup, }; } - // TODO Review 需要评估这里等待的变成模式是否正确高效 - public void OpenAnimation(Action? opened = null) + public async Task OpenAsync() { // AbstractPopup is currently open if (IsOpen || _openAnimating || _closeAnimating) { - return; + await Task.CompletedTask; } if (!IsMotionEnabled) { Open(); - opened?.Invoke(); - return; + await Task.CompletedTask; } _openAnimating = true; + + var tsc = new TaskCompletionSource(); + Open(); var popupRoot = Host as PopupRoot; @@ -492,14 +492,17 @@ public class Popup : AvaloniaPopup, Dispatcher.UIThread.Post(() => { popupRoot.Opacity = 1.0; - RunOpenAnimation(popupRoot, opened); + RunOpenAnimation(popupRoot, tsc); }); - return; } - RunOpenAnimation(popupRoot, opened); + else + { + RunOpenAnimation(popupRoot, tsc); + } + await tsc.Task; } - - private void RunOpenAnimation(PopupRoot popupRoot, Action? opened = null) + + private void RunOpenAnimation(PopupRoot popupRoot, TaskCompletionSource tsc) { var placementTarget = GetEffectivePlacementTarget(); var popupOffset = popupRoot.PlatformImpl!.Position; @@ -533,34 +536,34 @@ public class Popup : AvaloniaPopup, { _shadowLayer?.Open(); popupRoot.Show(); - opened?.Invoke(); _isNeedWaitFlipSync = false; _openAnimating = false; if (RequestCloseWhereAnimationCompleted) { RequestCloseWhereAnimationCompleted = false; - Dispatcher.UIThread.InvokeAsync(() => { CloseAnimation(); }); + Dispatcher.UIThread.InvokeAsync(async () => { await CloseAsync(); }); } + tsc.SetResult(); }); }); } - public void CloseAnimation(Action? closed = null) + public async Task CloseAsync() { if (_closeAnimating) { - return; + await Task.CompletedTask; } if (_openAnimating) { RequestCloseWhereAnimationCompleted = true; - return; + await Task.CompletedTask; } if (!IsOpen) { - return; + await Task.CompletedTask; } if (!IsMotionEnabled) @@ -568,11 +571,11 @@ public class Popup : AvaloniaPopup, _shadowLayer?.Close(); _isNeedDetectFlip = true; Close(); - closed?.Invoke(); - return; + await Task.CompletedTask; } _closeAnimating = true; + var tsc = new TaskCompletionSource(); var motion = new ZoomBigOutMotion(MotionDuration); @@ -602,16 +605,17 @@ public class Popup : AvaloniaPopup, { MotionInvoker.InvokeInPopupLayer(motionActor, motion, () => { - popupRoot.Hide(); _shadowLayer?.Close(); + popupRoot.Hide(); }, () => { _closeAnimating = false; _isNeedDetectFlip = true; Close(); - closed?.Invoke(); + tsc.SetResult(); }); }); + await tsc.Task; } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) diff --git a/src/AtomUI.Controls/Popup/PopupReflectionExtensions.cs b/src/AtomUI.Controls/Popup/PopupReflectionExtensions.cs index d39c06714..f39c4074a 100644 --- a/src/AtomUI.Controls/Popup/PopupReflectionExtensions.cs +++ b/src/AtomUI.Controls/Popup/PopupReflectionExtensions.cs @@ -1,4 +1,7 @@ +using System.Reflection; +using AtomUI.Reflection; using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives.PopupPositioning; @@ -6,6 +9,12 @@ namespace AtomUI.Controls; internal static class PopupReflectionExtensions { + #region 反射信息定义 + private static readonly Lazy SetPopupParentMethodInfo = new Lazy( + typeof(Popup).GetMethodInfoOrThrow("SetPopupParent", + BindingFlags.Instance | BindingFlags.NonPublic)); + #endregion + public static void MoveAndResize(this Popup popup, Point devicePoint, Size virtualSize) { if (popup.Host is PopupRoot popupRoot) @@ -17,4 +26,9 @@ internal static class PopupReflectionExtensions } } } + + public static void SetPopupParent(this Popup popup, Control? newParent) + { + SetPopupParentMethodInfo.Value.Invoke(popup, [newParent]); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Popup/PopupShadowLayer.cs b/src/AtomUI.Controls/Popup/PopupShadowLayer.cs index 57d316134..3dc2c4361 100644 --- a/src/AtomUI.Controls/Popup/PopupShadowLayer.cs +++ b/src/AtomUI.Controls/Popup/PopupShadowLayer.cs @@ -53,12 +53,32 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator private Popup _target; private ShadowLayerPopupOpenState? _openState; private ShadowRenderer? _shadowRenderer; - private bool _isOpenRequested; private IManagedPopupPositionerPopup? _managedPopupPositionerPopup; public PopupShadowLayer(Popup target) { _target = target; + // if (target is IPopupHostProvider popupHostProvider) + // { + // popupHostProvider.PopupHostChanged += host => + // { + // if (host is PopupRoot popupRoot) + // { + // popupRoot.PositionChanged += (sender, args) => + // { + // Console.WriteLine(args.Point); + // }; + // } + // }; + // } + + // if (_target.Host is PopupRoot targetPopupRoot) + // { + // targetPopupRoot.PositionChanged += (sender, args) => + // { + // Console.WriteLine(args.Point); + // }; + // } } private void HandleTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) @@ -133,19 +153,6 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator return new Size(targetWidth, targetHeight); } - private void HandleTargetOpened(object? sender, EventArgs args) - { - if (_isOpenRequested) - { - Open(); - } - } - - private void HandleTargetClosed(object? sender, EventArgs args) - { - Close(); - } - protected virtual void NotifyPopupHostCreated(IPopupHost popupHost) { } @@ -157,15 +164,7 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator { return; } - - if (_target.Host == null) - { - _isOpenRequested = true; - return; - } - - _isOpenRequested = false; - + var popupHost = OverlayPopupHost.CreatePopupHost(_target.PlacementTarget!, DependencyResolver.Instance); popupHost.Topmost = false; NotifyPopupHostCreated(popupHost); @@ -213,7 +212,7 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator if (targetPopupRoot != null) { SubscribeToEventHandler>(targetPopupRoot, - ParentPopupPositionChanged, + HandleParentPopupPositionChanged, (x, handler) => x.PositionChanged += handler, (x, handler) => x.PositionChanged -= handler).DisposeWith(handlerCleanup); } @@ -222,12 +221,6 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator HandleTargetPropertyChanged, (x, handler) => x.PropertyChanged += handler, (x, handler) => x.PropertyChanged -= handler).DisposeWith(handlerCleanup); - SubscribeToEventHandler(_target, HandleTargetOpened, - (x, handler) => x.Opened += handler, - (x, handler) => x.Opened -= handler).DisposeWith(handlerCleanup); - SubscribeToEventHandler>(_target, HandleTargetClosed, - (x, handler) => x.Closed += handler, - (x, handler) => x.Closed -= handler).DisposeWith(handlerCleanup); var cleanupPopup = Disposable.Create((popupHost, handlerCleanup), state => { @@ -248,7 +241,10 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator { Dispatcher.UIThread.Post(() => { - _target.Host.Topmost = true; + if (_target.Host is not null) + { + _target.Host.Topmost = true; + } targetPopupRoot.Activate(); }); } @@ -256,7 +252,6 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator public virtual void Close() { - _isOpenRequested = false; if (_openState is null) { return; @@ -289,11 +284,14 @@ internal class PopupShadowLayer : AvaloniaObject, IShadowDecorator } } - private void ParentPopupPositionChanged(object? src, PixelPointEventArgs e) => HandlePositionChange(); - - private void HandlePositionChange() + private void HandleParentPopupPositionChanged(object? src, PixelPointEventArgs e) { + if (src is PopupRoot targetPopupRoot) + { + UpdateLayoutHostPositionAndSize(targetPopupRoot); + } } + private void RootTemplateApplied(object? sender, TemplateAppliedEventArgs e) { diff --git a/src/AtomUI.Controls/Slider/Slider.cs b/src/AtomUI.Controls/Slider/Slider.cs index 5016e49de..00b7a4977 100644 --- a/src/AtomUI.Controls/Slider/Slider.cs +++ b/src/AtomUI.Controls/Slider/Slider.cs @@ -302,19 +302,19 @@ public class Slider : RangeBase, { if (SliderTrack.StartSliderThumb is not null) { - ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(Value)); + Avalonia.Controls.ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(Value)); } } else { if (SliderTrack.StartSliderThumb is not null) { - ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(RangeValue.StartValue)); + Avalonia.Controls.ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(RangeValue.StartValue)); } if (SliderTrack.EndSliderThumb is not null) { - ToolTip.SetTip(SliderTrack.EndSliderThumb, FormatValue(RangeValue.EndValue)); + Avalonia.Controls.ToolTip.SetTip(SliderTrack.EndSliderThumb, FormatValue(RangeValue.EndValue)); } } } @@ -616,24 +616,24 @@ public class Slider : RangeBase, { if (SliderTrack.StartSliderThumb is not null) { - ToolTip.SetPlacement(SliderTrack.StartSliderThumb, PlacementMode.Top); + Avalonia.Controls.ToolTip.SetPlacement(SliderTrack.StartSliderThumb, PlacementMode.Top); } if (SliderTrack.EndSliderThumb is not null) { - ToolTip.SetPlacement(SliderTrack.EndSliderThumb, PlacementMode.Top); + Avalonia.Controls.ToolTip.SetPlacement(SliderTrack.EndSliderThumb, PlacementMode.Top); } } else { if (SliderTrack.StartSliderThumb is not null) { - ToolTip.SetPlacement(SliderTrack.StartSliderThumb, PlacementMode.Right); + Avalonia.Controls.ToolTip.SetPlacement(SliderTrack.StartSliderThumb, PlacementMode.Right); } if (SliderTrack.EndSliderThumb is not null) { - ToolTip.SetPlacement(SliderTrack.EndSliderThumb, PlacementMode.Right); + Avalonia.Controls.ToolTip.SetPlacement(SliderTrack.EndSliderThumb, PlacementMode.Right); } } } @@ -653,7 +653,7 @@ public class Slider : RangeBase, { if (SliderTrack is not null && SliderTrack.StartSliderThumb is not null) { - ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(Value)); + Avalonia.Controls.ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(Value)); } } else if (change.Property == RangeValueProperty) @@ -662,12 +662,12 @@ public class Slider : RangeBase, { if (SliderTrack.StartSliderThumb is not null) { - ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(RangeValue.StartValue)); + Avalonia.Controls.ToolTip.SetTip(SliderTrack.StartSliderThumb, FormatValue(RangeValue.StartValue)); } if (SliderTrack.EndSliderThumb is not null) { - ToolTip.SetTip(SliderTrack.EndSliderThumb, FormatValue(RangeValue.EndValue)); + Avalonia.Controls.ToolTip.SetTip(SliderTrack.EndSliderThumb, FormatValue(RangeValue.EndValue)); } } } diff --git a/src/AtomUI.Controls/Slider/SliderTheme.cs b/src/AtomUI.Controls/Slider/SliderTheme.cs index 03473001b..1e5078721 100644 --- a/src/AtomUI.Controls/Slider/SliderTheme.cs +++ b/src/AtomUI.Controls/Slider/SliderTheme.cs @@ -31,8 +31,8 @@ internal class SliderTheme : BaseControlTheme }; CreateTemplateParentBinding(startSliderThumb, SliderThumb.IsMotionEnabledProperty, Slider.IsMotionEnabledProperty); - ToolTip.SetPlacement(startSliderThumb, PlacementMode.Top); - ToolTip.SetShowDelay(startSliderThumb, 0); + Avalonia.Controls.ToolTip.SetPlacement(startSliderThumb, PlacementMode.Top); + Avalonia.Controls.ToolTip.SetShowDelay(startSliderThumb, 0); startSliderThumb.RegisterInNameScope(scope); var endSliderThumb = new SliderThumb @@ -43,8 +43,8 @@ internal class SliderTheme : BaseControlTheme Slider.IsMotionEnabledProperty); endSliderThumb.RegisterInNameScope(scope); - ToolTip.SetPlacement(endSliderThumb, PlacementMode.Top); - ToolTip.SetShowDelay(endSliderThumb, 0); + Avalonia.Controls.ToolTip.SetPlacement(endSliderThumb, PlacementMode.Top); + Avalonia.Controls.ToolTip.SetShowDelay(endSliderThumb, 0); var sliderTrack = new SliderTrack { diff --git a/src/AtomUI.Controls/Tooltip/ToolTip.cs b/src/AtomUI.Controls/Tooltip/ToolTip.cs index 710effa08..922f4645d 100644 --- a/src/AtomUI.Controls/Tooltip/ToolTip.cs +++ b/src/AtomUI.Controls/Tooltip/ToolTip.cs @@ -1,90 +1,57 @@ -using AtomUI.Reflection; +using System.Diagnostics; +using System.Reactive.Disposables; using AtomUI.Theme; -using AtomUI.Theme.Data; using AtomUI.Theme.Palette; -using AtomUI.Theme.Styling; +using AtomUI.Theme.Utils; +using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives.PopupPositioning; -using Avalonia.LogicalTree; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Threading; namespace AtomUI.Controls; [PseudoClasses(StdPseudoClass.Open)] -public class ToolTip : TemplatedControl, - IShadowMaskInfoProvider, - IControlSharedTokenResourcesHost +public class ToolTip : ContentControl, + IControlSharedTokenResourcesHost, + IAnimationAwareControl, + IPopupHostProvider, + IShadowMaskInfoProvider { #region 公共属性定义 - /// - /// Defines the property. - /// - public static readonly StyledProperty ContentProperty = - AvaloniaProperty.Register(nameof(Content)); - - /// - /// Defines the ToolTip.Tip attached property. - /// public static readonly AttachedProperty TipProperty = AvaloniaProperty.RegisterAttached("Tip"); - /// - /// Defines the ToolTip.IsOpen attached property. - /// public static readonly AttachedProperty IsOpenProperty = AvaloniaProperty.RegisterAttached("IsOpen"); - /// - /// 自己手动关闭哦 - /// - public static readonly AttachedProperty IsCustomHideProperty = - AvaloniaProperty.RegisterAttached("IsCustomHide"); - - /// - /// Defines the ToolTip.PresetColor attached property. - /// public static readonly AttachedProperty PresetColorProperty = AvaloniaProperty.RegisterAttached("PresetColor"); - /// - /// Defines the ToolTip.PresetColor attached property. - /// public static readonly AttachedProperty ColorProperty = AvaloniaProperty.RegisterAttached("Color"); - /// - /// 是否显示指示箭头 - /// public static readonly AttachedProperty IsShowArrowProperty = AvaloniaProperty.RegisterAttached("IsShowArrow", true); - /// - /// 箭头是否始终指向中心 - /// public static readonly AttachedProperty IsPointAtCenterProperty = AvaloniaProperty.RegisterAttached("IsPointAtCenter"); - /// - /// Defines the ToolTip.Placement property. - /// public static readonly AttachedProperty PlacementProperty = AvaloniaProperty.RegisterAttached( "Placement", PlacementMode.Top); - /// - /// Defines the ToolTip.HorizontalOffset property. - /// public static readonly AttachedProperty HorizontalOffsetProperty = AvaloniaProperty.RegisterAttached("HorizontalOffset"); - /// - /// Defines the ToolTip.VerticalOffset property. - /// public static readonly AttachedProperty VerticalOffsetProperty = AvaloniaProperty.RegisterAttached("VerticalOffset"); @@ -92,245 +59,199 @@ public class ToolTip : TemplatedControl, /// 距离 anchor 的边距,根据垂直和水平进行设置 /// public static readonly AttachedProperty MarginToAnchorProperty = - AvaloniaProperty.RegisterAttached("MarginToAnchor", double.NaN); + AvaloniaProperty.RegisterAttached("MarginToAnchor", 2); - /// - /// Defines the ToolTip.ShowDelay property. - /// public static readonly AttachedProperty ShowDelayProperty = AvaloniaProperty.RegisterAttached("ShowDelay", 400); - /// - /// Defines the ToolTip.BetweenShowDelay property. - /// public static readonly AttachedProperty BetweenShowDelayProperty = AvaloniaProperty.RegisterAttached("BetweenShowDelay", 100); - /// - /// Defines the ToolTip.ShowOnDisabled property. - /// public static readonly AttachedProperty ShowOnDisabledProperty = AvaloniaProperty.RegisterAttached("ShowOnDisabled", false, true); - /// - /// Defines the ToolTip.ServiceEnabled property. - /// public static readonly AttachedProperty ServiceEnabledProperty = AvaloniaProperty.RegisterAttached("ServiceEnabled", true, true); - public object? Content + public static readonly AttachedProperty CustomPopupPlacementCallbackProperty = + AvaloniaProperty.RegisterAttached( + "CustomPopupPlacementCallback"); + + public static readonly AttachedProperty IsCustomHideProperty = + AvaloniaProperty.RegisterAttached("IsCustomHide"); + + public static readonly StyledProperty IsMotionEnabledProperty + = AvaloniaProperty.Register(nameof(IsMotionEnabled)); + + public static readonly StyledProperty IsWaveAnimationEnabledProperty + = AvaloniaProperty.Register(nameof(IsWaveAnimationEnabled)); + + public bool IsMotionEnabled { - get => GetValue(ContentProperty); - set => SetValue(ContentProperty, value); + get => GetValue(IsMotionEnabledProperty); + set => SetValue(IsMotionEnabledProperty, value); + } + + public bool IsWaveAnimationEnabled + { + get => GetValue(IsWaveAnimationEnabledProperty); + set => SetValue(IsWaveAnimationEnabledProperty, value); } #endregion - #region 内部属性定义 + #region 附加属性设置方法定义 - /// - /// Stores the current instance in the control. - /// - internal static readonly AttachedProperty ToolTipProperty = - AvaloniaProperty.RegisterAttached("ToolTip"); - - internal static readonly StyledProperty IsShowArrowEffectiveProperty = - ArrowDecoratedBox.IsShowArrowProperty.AddOwner(); - - /// - /// 是否实际显示箭头 - /// - internal bool IsShowArrowEffective - { - get => GetValue(IsShowArrowEffectiveProperty); - set => SetValue(IsShowArrowEffectiveProperty, value); - } - - internal static readonly StyledProperty ShadowsProperty - = AvaloniaProperty.Register(nameof(Shadows)); - - internal static readonly StyledProperty MotionDurationProperty - = AvaloniaProperty.Register(nameof(MotionDuration)); - - internal static readonly StyledProperty DefaultMarginToAnchorProperty - = AvaloniaProperty.Register(nameof(DefaultMarginToAnchor)); - - internal BoxShadows Shadows - { - get => GetValue(ShadowsProperty); - set => SetValue(ShadowsProperty, value); - } - - internal TimeSpan MotionDuration - { - get => GetValue(MotionDurationProperty); - set => SetValue(MotionDurationProperty, value); - } - - internal double DefaultMarginToAnchor - { - get => GetValue(DefaultMarginToAnchorProperty); - set => SetValue(DefaultMarginToAnchorProperty, value); - } - - internal static readonly DirectProperty FlipPlacementProperty = - AvaloniaProperty.RegisterDirect(nameof(FlipPlacement), - o => o.FlipPlacement, - (o, v) => o.FlipPlacement = v); - - private PlacementMode? _flipPlacement; - - internal PlacementMode? FlipPlacement - { - get => _flipPlacement; - set => SetAndRaise(FlipPlacementProperty, ref _flipPlacement, value); - } - - Control IControlSharedTokenResourcesHost.HostControl => this; - string IControlSharedTokenResourcesHost.TokenId => ToolTipToken.ID; - - #endregion - - static ToolTip() - { - IsOpenProperty.Changed.Subscribe(IsOpenChanged); - IsShowArrowProperty.Changed.Subscribe(IsShowArrowChanged); - AffectsRender(ForegroundProperty, BackgroundProperty); - AffectsArrange(FlipPlacementProperty, IsShowArrowProperty); - } - - #region 附加属性设置方法 - - /// - /// Gets the value of the ToolTipOld.Tip attached property. - /// - /// The control to get the property from. - /// - /// The content to be displayed in the control's tooltip. - /// public static object? GetTip(Control element) { return element.GetValue(TipProperty); } - /// - /// Sets the value of the ToolTipOld.Tip attached property. - /// - /// The control to get the property from. - /// The content to be displayed in the control's tooltip. public static void SetTip(Control element, object? value) { element.SetValue(TipProperty, value); } - /// - /// Gets the value of the ToolTipOld.IsOpen attached property. - /// - /// The control to get the property from. - /// - /// A value indicating whether the tool tip is visible. - /// public static bool GetIsOpen(Control element) { return element.GetValue(IsOpenProperty); } - /// - /// Sets the value of the ToolTipOld.IsOpen attached property. - /// - /// The control to get the property from. - /// A value indicating whether the tool tip is visible. public static void SetIsOpen(Control element, bool value) { element.SetValue(IsOpenProperty, value); } - /// - /// Gets the value of the ToolTipOld.Placement attached property. - /// - /// The control to get the property from. - /// - /// A value indicating how the tool tip is positioned. - /// - public static PlacementMode GetPlacement(Control element) + public static PresetColorType? GetPresetColor(Control element) { - return element.GetValue(PlacementProperty); + return element.GetValue(PresetColorProperty); } - /// - /// Sets the value of the ToolTipOld.Placement attached property. - /// - /// The control to get the property from. - /// A value indicating how the tool tip is positioned. - public static void SetPlacement(Control element, PlacementMode value) + public static void SetPresetColor(Control element, PresetColorType color) { - element.SetValue(PlacementProperty, value); + element.SetValue(PresetColorProperty, color); } - /// - /// Gets the value of the ToolTip.HorizontalOffset attached property. - /// - /// The control to get the property from. - /// - /// A value indicating how the tool tip is positioned. - /// - public static double GetHorizontalOffset(Control element) + public static Color? GetColor(Control element) { - return element.GetValue(HorizontalOffsetProperty); + return element.GetValue(ColorProperty); } - /// - /// Sets the value of the ToolTip.HorizontalOffset attached property. - /// - /// The control to get the property from. - /// A value indicating how the tool tip is positioned. - public static void SetHorizontalOffset(Control element, double value) + public static void SetColor(Control element, Color color) { - element.SetValue(HorizontalOffsetProperty, value); + element.SetValue(ColorProperty, color); } - /// - /// Gets the value of the ToolTip.VerticalOffset attached property. - /// - /// The control to get the property from. - /// - /// A value indicating how the tool tip is positioned. - /// - public static double GetVerticalOffset(Control element) - { - return element.GetValue(VerticalOffsetProperty); - } - - /// - /// Sets the value of the ToolTip.VerticalOffset attached property. - /// - /// The control to get the property from. - /// A value indicating how the tool tip is positioned. - public static void SetVerticalOffset(Control element, double value) - { - element.SetValue(VerticalOffsetProperty, value); - } - - /// - /// 是否显示箭头 - /// - /// - /// public static bool GetIsShowArrow(Control element) { return element.GetValue(IsShowArrowProperty); } - /// - /// 设置是否显示箭头 - /// - /// - /// public static void SetIsShowArrow(Control element, bool flag) { element.SetValue(IsShowArrowProperty, flag); } + public static bool GetIsPointAtCenter(Control element) + { + return element.GetValue(IsPointAtCenterProperty); + } + + public static void SetIsPointAtCenter(Control element, bool flag) + { + element.SetValue(IsPointAtCenterProperty, flag); + } + + public static PlacementMode GetPlacement(Control element) + { + return element.GetValue(PlacementProperty); + } + + public static void SetPlacement(Control element, PlacementMode value) + { + element.SetValue(PlacementProperty, value); + } + + public static double GetHorizontalOffset(Control element) + { + return element.GetValue(HorizontalOffsetProperty); + } + + public static void SetHorizontalOffset(Control element, double value) + { + element.SetValue(HorizontalOffsetProperty, value); + } + + public static double GetVerticalOffset(Control element) + { + return element.GetValue(VerticalOffsetProperty); + } + + public static void SetVerticalOffset(Control element, double value) + { + element.SetValue(VerticalOffsetProperty, value); + } + + public static double GetMarginToAnchor(Control element) + { + return element.GetValue(MarginToAnchorProperty); + } + + public static void SetMarginToAnchor(Control element, double margin) + { + element.SetValue(MarginToAnchorProperty, margin); + } + + public static int GetShowDelay(Control element) + { + return element.GetValue(ShowDelayProperty); + } + + public static void SetShowDelay(Control element, int value) + { + element.SetValue(ShowDelayProperty, value); + } + + public static int GetBetweenShowDelay(Control element) + { + return element.GetValue(BetweenShowDelayProperty); + } + + public static void SetBetweenShowDelay(Control element, int value) + { + element.SetValue(BetweenShowDelayProperty, value); + } + + public static bool GetShowOnDisabled(Control element) + { + return element.GetValue(ShowOnDisabledProperty); + } + + public static void SetShowOnDisabled(Control element, bool value) + { + element.SetValue(ShowOnDisabledProperty, value); + } + + public static bool GetServiceEnabled(Control element) + { + return element.GetValue(ServiceEnabledProperty); + } + + public static void SetServiceEnabled(Control element, bool value) + { + element.SetValue(ServiceEnabledProperty, value); + } + + public static CustomPopupPlacementCallback? GetCustomPopupPlacementCallback(Control element) + { + return element.GetValue(CustomPopupPlacementCallbackProperty); + } + + public static void SetCustomPopupPlacementCallback(Control element, CustomPopupPlacementCallback? value) + { + element.SetValue(CustomPopupPlacementCallbackProperty, value); + } + public static bool GetIsCustomHide(Control element) { return element.GetValue(IsCustomHideProperty); @@ -341,309 +262,87 @@ public class ToolTip : TemplatedControl, element.SetValue(IsCustomHideProperty, flag); } - /// - /// 箭头是否始终指向居中位置 - /// - /// - /// - public static bool GetIsPointAtCenter(Control element) - { - return element.GetValue(IsPointAtCenterProperty); - } - - /// - /// 设置箭头始终指向居中位置 - /// - /// - /// - public static void SetIsPointAtCenter(Control element, bool flag) - { - element.SetValue(IsPointAtCenterProperty, flag); - } - - /// - /// ToolTipOld Anchor 目标控件的边距 - /// - /// - /// - public static double GetMarginToAnchor(Control element) - { - return element.GetValue(MarginToAnchorProperty); - } - - /// - /// 设置 ToolTipOld Anchor 目标控件的边距 - /// - /// - /// - public static void SetMarginToAnchor(Control element, double margin) - { - element.SetValue(MarginToAnchorProperty, margin); - } - - /// - /// Gets the value of the ToolTipOld.ShowDelay attached property. - /// - /// The control to get the property from. - /// - /// A value indicating the time, in milliseconds, before a tool tip opens. - /// - public static int GetShowDelay(Control element) - { - return element.GetValue(ShowDelayProperty); - } - - /// - /// Sets the value of the ToolTipOld.ShowDelay attached property. - /// - /// The control to get the property from. - /// A value indicating the time, in milliseconds, before a tool tip opens. - public static void SetShowDelay(Control element, int value) - { - element.SetValue(ShowDelayProperty, value); - } - - /// - /// Gets the number of milliseconds since the last tooltip closed during which the tooltip of - /// will open immediately, - /// or a negative value indicating that the tooltip will always wait for before - /// opening. - /// - /// The control to get the property from. - public static int GetBetweenShowDelay(Control element) - { - return element.GetValue(BetweenShowDelayProperty); - } - - /// - /// Sets the number of milliseconds since the last tooltip closed during which the tooltip of - /// will open immediately. - /// - /// - /// Setting a negative value disables the immediate opening behaviour. The tooltip of will - /// then always wait until - /// elapses before showing. - /// - /// The control to get the property from. - /// The number of milliseconds to set, or a negative value to disable the behaviour. - public static void SetBetweenShowDelay(Control element, int value) - { - element.SetValue(BetweenShowDelayProperty, value); - } - - /// - /// Gets whether a control will display a tooltip even if it disabled. - /// - /// The control to get the property from. - public static bool GetShowOnDisabled(Control element) - { - return element.GetValue(ShowOnDisabledProperty); - } - - /// - /// Sets whether a control will display a tooltip even if it disabled. - /// - /// The control to get the property from. - /// Whether the control is to display a tooltip even if it disabled. - public static void SetShowOnDisabled(Control element, bool value) - { - element.SetValue(ShowOnDisabledProperty, value); - } - - /// - /// Gets whether showing and hiding of a control's tooltip will be automatically controlled by Avalonia. - /// - /// The control to get the property from. - public static bool GetServiceEnabled(Control element) - { - return element.GetValue(ServiceEnabledProperty); - } - - /// - /// Sets whether showing and hiding of a control's tooltip will be automatically controlled by Avalonia. - /// - /// The control to get the property from. - /// Whether the control is to display a tooltip even if it disabled. - public static void SetServiceEnabled(Control element, bool value) - { - element.SetValue(ServiceEnabledProperty, value); - } - - /// - /// 获取预设置的颜色 - /// - /// - /// - public static PresetColorType? GetPresetColor(Control element) - { - return element.GetValue(PresetColorProperty); - } - - /// - /// 设置预设颜色 - /// - /// - /// - public static void SetPresetColor(Control element, PresetColorType color) - { - element.SetValue(PresetColorProperty, color); - } - - /// - /// 获取预设置的颜色 - /// - /// - /// - public static Color? GetColor(Control element) - { - return element.GetValue(ColorProperty); - } - - /// - /// 设置预设颜色 - /// - /// - /// - public static void SetColor(Control element, Color color) - { - element.SetValue(ColorProperty, color); - } - #endregion - private Popup? _popup; - private Action? _popupHostChangedHandler; - private ArrowDecoratedBox? _arrowDecoratedBox; - internal Control? AdornedControl { get; private set; } - internal event EventHandler? Closed; + #region 公共事件定义 - private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) - { - var control = (Control)e.Sender; - var newValue = (bool)e.NewValue!; + public static readonly RoutedEvent ToolTipOpeningEvent = + RoutedEvent.Register("ToolTipOpening", RoutingStrategies.Direct); - if (newValue) - { - var tip = GetTip(control); - if (tip == null) - { - return; - } + public static readonly RoutedEvent ToolTipClosingEvent = + RoutedEvent.Register("ToolTipClosing", RoutingStrategies.Direct); - var toolTip = control.GetValue(ToolTipProperty); - if (toolTip == null || (tip != toolTip && tip != toolTip.Content)) - { - toolTip?.Close(); - toolTip = tip as ToolTip ?? new ToolTip - { - Content = tip - }; - control.SetValue(ToolTipProperty, toolTip); - } - - toolTip.AdornedControl = control; - toolTip.Open(control); - toolTip.UpdatePseudoClasses(newValue); - } - else if (control.GetValue(ToolTipProperty) is { } toolTip) - { - toolTip.AdornedControl = null; - toolTip.Close(); - toolTip.UpdatePseudoClasses(newValue); - } - } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - if (change.Property == ContentProperty) - { - if (_arrowDecoratedBox is not null) - { - if (_arrowDecoratedBox.Content is TextBlock textBlock) - { - if (change.NewValue is string text) - { - textBlock.Text = text; - } - } - } - } - } - - private static void IsShowArrowChanged(AvaloniaPropertyChangedEventArgs e) - { - var control = (Control)e.Sender; - var toolTip = control.GetValue(ToolTipProperty); - if (toolTip is not null) - { - // 重新配置 - toolTip.Close(); - control.SetValue(ToolTipProperty, null); - } - } - - internal IPopupHost? PopupHost => _popup?.Host; - - private event Action? PopupHostChanged + event Action? IPopupHostProvider.PopupHostChanged { add => _popupHostChangedHandler += value; remove => _popupHostChangedHandler -= value; } - private void SetToolTipColor(Control control) + public static void AddToolTipOpeningHandler(Control element, EventHandler handler) { - // Preset 优先级高 - var presetColorType = GetPresetColor(control); - var color = GetColor(control); - if (presetColorType is not null) - { - var presetColor = PresetPrimaryColor.GetColor(presetColorType.Value); - _arrowDecoratedBox?.SetValue(BackgroundProperty, new SolidColorBrush(presetColor.Color())); - } - else if (color is not null) - { - _arrowDecoratedBox?.SetValue(BackgroundProperty, new SolidColorBrush(color.Value)); - } + element.AddHandler(ToolTipOpeningEvent, handler); } - /// - /// Helper method to set popup's styling and templated parent. - /// - internal void SetPopupParent(Popup popup, Control? newParent) + public static void RemoveToolTipOpeningHandler(Control element, EventHandler handler) { - if (popup.Parent != null && popup.Parent != newParent) - { - ((ISetLogicalParent)popup).SetParent(null); - } - - if (popup.Parent == null || popup.PlacementTarget != newParent) - { - ((ISetLogicalParent)popup).SetParent(newParent); - } + element.RemoveHandler(ToolTipOpeningEvent, handler); } - private static bool TryGetAdorner(Visual target, out Visual? adorned, out Visual? adornerLayer) + public static void AddToolTipClosingHandler(Control element, EventHandler handler) { - var element = target; - while (element != null) - { - if (AdornerLayer.GetAdornedElement(element) is { } adornedElement) - { - adorned = adornedElement; - adornerLayer = AdornerLayer.GetAdornerLayer(adorned); - return true; - } - - element = element.GetPropertyOrThrow("VisualParent"); - } - - adorned = null; - adornerLayer = null; - return false; + element.AddHandler(ToolTipClosingEvent, handler); + } + + public static void RemoveToolTipClosingHandler(Control element, EventHandler handler) + { + element.RemoveHandler(ToolTipClosingEvent, handler); + } + + #endregion + + #region 内部属性定义 + + internal static readonly AttachedProperty ToolTipProperty = + AvaloniaProperty.RegisterAttached("ToolTip"); + + Control IControlSharedTokenResourcesHost.HostControl => this; + string IControlSharedTokenResourcesHost.TokenId => ToolTipToken.ID; + Control IAnimationAwareControl.PropertyBindTarget => this; + + #endregion + + internal Control? AdornedControl { get; private set; } + internal event EventHandler? Closed; + internal IPopupHost? PopupHost => _popup?.Host; + IPopupHost? IPopupHostProvider.PopupHost => _popup?.Host; + + private Popup? _popup; + private Action? _popupHostChangedHandler; + private CompositeDisposable? _subscriptions; + private ArrowDecoratedBox? _arrowDecoratedBox; + + static ToolTip() + { + IsOpenProperty.Changed.Subscribe(HandleIsOpenChanged); + } + + public ToolTip() + { + this.BindAnimationProperties(IsMotionEnabledProperty, IsWaveAnimationEnabledProperty); + } + + public CornerRadius GetMaskCornerRadius() + { + Debug.Assert(_arrowDecoratedBox != null); + return _arrowDecoratedBox.GetMaskCornerRadius(); + } + + public Rect GetMaskBounds() + { + Debug.Assert(_arrowDecoratedBox != null); + return _arrowDecoratedBox.GetMaskBounds(); } - // TODO 当设置跟随鼠标的时候,需要特殊处理 private void Open(Control control) { Close(); @@ -652,160 +351,90 @@ public class ToolTip : TemplatedControl, { _popup = new Popup { - WindowManagerAddShadowHint = false, - IsLightDismissEnabled = false, + WindowManagerAddShadowHint = false, + IsLightDismissEnabled = false, OverlayDismissEventPassThrough = false }; - _popup.Child = this; + _popup.Child = this; + _popup.TakesFocusFromNativeControl = false; - _popup.Opened += OnPopupOpened; - _popup.Closed += OnPopupClosed; - _popup.PositionFlipped += OnPopupPositionFlipped; + _popup.Opened += HandlePopupOpened; + _popup.Closed += HandlePopupClosed; + _popup.PositionFlipped += HandlePopupPositionFlipped; } - - SetPopupParent(_popup, control); - - TokenResourceBinder.CreateTokenBinding(_popup, Popup.MaskShadowsProperty, - SharedTokenKey.BoxShadowsSecondary); - SetToolTipColor(control); - - var marginToAnchor = GetMarginToAnchor(control); - if (double.IsNaN(marginToAnchor)) - { - marginToAnchor = DefaultMarginToAnchor; - } - - var placement = GetPlacement(control); - - var offsetX = GetHorizontalOffset(control); - var offsetY = GetVerticalOffset(control); - - _popup.HorizontalOffset = offsetX; - _popup.VerticalOffset = offsetY; - + if (_popup is IPopupHostProvider popupHostProvider) { popupHostProvider.PopupHostChanged += HandlePopupHostChanged; } + + // 这里评估是 tooltip 会多次复用,所以这里把绑定管理起来,关闭的时候释放 + // 不知道对不对 + _subscriptions = new CompositeDisposable(new[] + { + _popup.Bind(Popup.HorizontalOffsetProperty, control.GetBindingObservable(HorizontalOffsetProperty)), + _popup.Bind(Popup.VerticalOffsetProperty, control.GetBindingObservable(VerticalOffsetProperty)), + _popup.Bind(Popup.PlacementProperty, control.GetBindingObservable(PlacementProperty)), + _popup.Bind(Popup.MarginToAnchorProperty, control.GetBindingObservable(MarginToAnchorProperty)), + _popup.Bind(Popup.CustomPopupPlacementCallbackProperty, + control.GetBindingObservable(CustomPopupPlacementCallbackProperty)), + }); + + var placement = GetPlacement(control); + var anchorAndGravity = PopupUtils.GetAnchorAndGravity(placement); - // TODO 可能是多余的,因为有那个对反转事件的处理 - SetupArrowPosition(placement); - // 后期看能不能检测对应字段的改变 - CalculateShowArrowEffective(control); - - _popup.MarginToAnchor = marginToAnchor; - _popup.Placement = placement; _popup.PlacementTarget = control; - - _popup.OpenAnimation(); - } - - private void HandlePopupHostChanged(IPopupHost? host) - { - if (_popup is not null) + _popup.Placement = placement; + _popup.PlacementAnchor = anchorAndGravity.Item1; + _popup.PlacementGravity = anchorAndGravity.Item2; + _popup.SetPopupParent(control); + + if (_arrowDecoratedBox is not null) { - var control = _popup.PlacementTarget; - if (control is not null) - { - var placement = GetPlacement(control); - var anchorAndGravity = PopupUtils.GetAnchorAndGravity(placement); - SetupPointCenterOffset(control, - placement, - anchorAndGravity.Item1, - anchorAndGravity.Item2); - } - } - } - - private bool CalculateShowArrowEffective(Control control) - { - if (GetIsShowArrow(control) == false) - { - IsShowArrowEffective = false; + SetupArrowDecoratedBox(control); } else { - IsShowArrowEffective = PopupUtils.CanEnabledArrow(GetPlacement(control)); + TemplateApplied += DeferSetupArrowDecoratedBox; } - return IsShowArrowEffective; - } - - private void Close() - { - if (_popup is null) + Dispatcher.UIThread.InvokeAsync(async () => { - return; - } + await _popup.OpenAsync(); + }); + } - if (_popup is IPopupHostProvider popupHostProvider) + private void DeferSetupArrowDecoratedBox(object? sender, TemplateAppliedEventArgs args) + { + TemplateApplied -= DeferSetupArrowDecoratedBox; + Debug.Assert(_popup != null && _popup.PlacementTarget != null); + SetupArrowDecoratedBox(_popup.PlacementTarget); + } + + private void SetupArrowDecoratedBox(Control control) + { + if (_arrowDecoratedBox is not null) { - popupHostProvider.PopupHostChanged -= HandlePopupHostChanged; - } - - _popup.CloseAnimation(); - } - - private void OnPopupPositionFlipped(object? sender, PopupFlippedEventArgs e) - { - if (sender is Popup popup) - { - SetupArrowPosition(popup.Placement); + SetToolTipColor(control); + _subscriptions?.Add(_arrowDecoratedBox.Bind(ArrowDecoratedBox.IsShowArrowProperty, + control.GetBindingObservable(IsShowArrowProperty, flag => + { + // 有些条件下是不能开启箭头指针的 + if (flag && _popup is not null) + { + return PopupUtils.CanEnabledArrow(_popup.Placement, _popup.PlacementAnchor, + _popup.PlacementGravity); + } + return flag; + }))); + if (_popup is not null) + { + SetupArrowPosition(_popup.Placement, _popup.PlacementAnchor, + _popup.PlacementGravity); + } } } - - private void OnPopupClosed(object? sender, EventArgs e) - { - // This condition is true, when Popup was closed by any other reason outside of ToolTipService/ToolTipOld, keeping IsOpen=true. - if (AdornedControl is { } adornedControl - && GetIsOpen(adornedControl)) - { - adornedControl.SetCurrentValue(IsOpenProperty, false); - } - - _popupHostChangedHandler?.Invoke(null); - Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnPopupOpened(object? sender, EventArgs e) - { - _popupHostChangedHandler?.Invoke(((Popup)sender!).Host); - } - - private void UpdatePseudoClasses(bool newValue) - { - PseudoClasses.Set(StdPseudoClass.Open, newValue); - } - - protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) - { - base.OnAttachedToLogicalTree(e); - // 手动提前应用模板,因为有某些计算需要 - ApplyStyling(); - ApplyTemplate(); - } - - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - HandleTemplateApplied(e.NameScope); - } - - private void HandleTemplateApplied(INameScope scope) - { - _arrowDecoratedBox = scope.Find(ToolTipTheme.ToolTipContainerPart); - } - - public CornerRadius GetMaskCornerRadius() - { - return _arrowDecoratedBox!.GetMaskCornerRadius(); - } - - public Rect GetMaskBounds() - { - return _arrowDecoratedBox!.GetMaskBounds(); - } - + private void SetupArrowPosition(PlacementMode placement, PopupAnchor? anchor = null, PopupGravity? gravity = null) { var arrowPosition = PopupUtils.CalculateArrowPosition(placement, anchor, gravity); @@ -814,29 +443,166 @@ public class ToolTip : TemplatedControl, _arrowDecoratedBox.ArrowPosition = arrowPosition.Value; } } - - private void SetupPointCenterOffset(Control placementTarget, PlacementMode placement, PopupAnchor? anchor = null, - PopupGravity? gravity = null) + + private void HandlePopupPositionFlipped(object? sender, PopupFlippedEventArgs e) { - var offset = - CalculatePopupPositionDelta(placementTarget, placement, anchor, gravity); - _popup!.HorizontalOffset += offset.X; - _popup.VerticalOffset += offset.Y; + if (sender is Popup popup) + { + SetupArrowPosition(popup.Placement, popup.PlacementAnchor, popup.PlacementGravity); + } } - private Point CalculatePopupPositionDelta(Control anchorTarget, PlacementMode placement, PopupAnchor? anchor = null, + private void Close() + { + if (AdornedControl is { } adornedControl + && GetIsOpen(adornedControl)) + { + var args = new RoutedEventArgs(ToolTipClosingEvent); + adornedControl.RaiseEvent(args); + } + + Dispatcher.UIThread.InvokeAsync(async () => + { + if (_popup is not null) + { + await _popup.CloseAsync(); + _popup.SetPopupParent(null); + _popup.PlacementTarget = null; + if (_popup is IPopupHostProvider popupHostProvider) + { + popupHostProvider.PopupHostChanged -= HandlePopupHostChanged; + } + } + _subscriptions?.Dispose(); + }); + } + + private void SetToolTipColor(Control control) + { + // Preset 优先级高 + if (_arrowDecoratedBox is not null) + { + var presetColorType = GetPresetColor(control); + var color = GetColor(control); + if (presetColorType is not null) + { + var presetColor = PresetPrimaryColor.GetColor(presetColorType.Value); + _arrowDecoratedBox.Background = new SolidColorBrush(presetColor.Color()); + InvalidateVisual(); + } + else if (color is not null) + { + _arrowDecoratedBox.Background = new SolidColorBrush(color.Value); + InvalidateVisual(); + } + } + } + + private static void HandleIsOpenChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; + var newValue = (bool)e.NewValue!; + + if (newValue) + { + var args = new CancelRoutedEventArgs(ToolTipOpeningEvent); + control.RaiseEvent(args); + if (args.Cancel) + { + control.SetCurrentValue(IsOpenProperty, false); + return; + } + + var tip = GetTip(control); + if (tip == null) + { + control.SetCurrentValue(IsOpenProperty, false); + return; + } + + var toolTip = control.GetValue(ToolTipProperty); + if (toolTip == null || (tip != toolTip && tip != toolTip.Content)) + { + toolTip?.Close(); + + toolTip = tip as ToolTip ?? new ToolTip { Content = tip }; + control.SetValue(ToolTipProperty, toolTip); + } + + toolTip.AdornedControl = control; + toolTip.Open(control); + } + else if (control.GetValue(ToolTipProperty) is { } toolTip) + { + toolTip.AdornedControl = null; + toolTip.Close(); + } + } + + private void HandlePopupHostChanged(IPopupHost? host) + { + if (_popup is not null) + { + var control = _popup.PlacementTarget; + if (control is not null) + { + // 如果指定这个,那么用户指定的 offset 失效 + var offset = CalculatePopupPositionDelta(control, _popup.Placement, _popup.PlacementAnchor, _popup.PlacementGravity); + control.SetValue(HorizontalOffsetProperty, offset.X); + control.SetValue(VerticalOffsetProperty, offset.Y); + } + } + } + + private void HandlePopupClosed(object? sender, EventArgs e) + { + // This condition is true, when Popup was closed by any other reason outside of ToolTipService/ToolTip, keeping IsOpen=true. + if (AdornedControl is { } adornedControl + && GetIsOpen(adornedControl)) + { + adornedControl.SetCurrentValue(IsOpenProperty, false); + } + + _popupHostChangedHandler?.Invoke(null); + UpdatePseudoClasses(false); + Closed?.Invoke(this, EventArgs.Empty); + } + + private void HandlePopupOpened(object? sender, EventArgs e) + { + _popupHostChangedHandler?.Invoke(((Popup)sender!).Host); + UpdatePseudoClasses(true); + } + + private void UpdatePseudoClasses(bool newValue) + { + PseudoClasses.Set(StdPseudoClass.Open, newValue); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _arrowDecoratedBox = e.NameScope.Find(ToolTipTheme.ToolTipContainerPart); + } + + private Point CalculatePopupPositionDelta(Control control, + PlacementMode placement, + PopupAnchor? anchor = null, PopupGravity? gravity = null) { - var offsetX = 0d; - var offsetY = 0d; - if (GetIsShowArrow(anchorTarget) && GetIsPointAtCenter(anchorTarget)) + var offsetX = 0d; + var offsetY = 0d; + var isShowArrow = GetIsShowArrow(control); + var isPointAtCenter = GetIsPointAtCenter(control); + if (isShowArrow && isPointAtCenter && _arrowDecoratedBox is not null) { if (PopupUtils.CanEnabledArrow(placement, anchor, gravity)) { - var arrowVertexPoint = _arrowDecoratedBox!.ArrowVertexPoint; - var anchorSize = anchorTarget.Bounds.Size; - var centerX = anchorSize.Width / 2; - var centerY = anchorSize.Height / 2; + var arrowVertexPoint = _arrowDecoratedBox.ArrowVertexPoint; + + var anchorSize = control.Bounds.Size; + var centerX = anchorSize.Width / 2; + var centerY = anchorSize.Height / 2; // 这里计算不需要全局坐标 if (placement == PlacementMode.TopEdgeAlignedLeft || placement == PlacementMode.BottomEdgeAlignedLeft) diff --git a/src/AtomUI.Controls/Tooltip/ToolTipService.cs b/src/AtomUI.Controls/Tooltip/ToolTipService.cs index 6247ee848..3f5ec9ac0 100644 --- a/src/AtomUI.Controls/Tooltip/ToolTipService.cs +++ b/src/AtomUI.Controls/Tooltip/ToolTipService.cs @@ -1,8 +1,9 @@ using System.Reactive.Disposables; +using AtomUI.Controls.Utils; using AtomUI.MotionScene; -using AtomUI.Reflection; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Threading; @@ -28,8 +29,8 @@ internal sealed class ToolTipService : IDisposable var inputManager = AvaloniaLocator.Current.GetService()!; _subscriptions = new CompositeDisposable( inputManager.Process.Subscribe(HandleInputManagerOnProcess), - ToolTip.ServiceEnabledProperty.Changed.Subscribe(ServiceEnabledChanged), - ToolTip.TipProperty.Changed.Subscribe(TipChanged)); + ToolTip.ServiceEnabledProperty.Changed.Subscribe(HandleServiceEnabledChanged), + ToolTip.TipProperty.Changed.Subscribe(HandleTipChanged)); } public void Dispose() @@ -59,9 +60,7 @@ internal sealed class ToolTipService : IDisposable { case RawPointerEventType.Move: var inputHitTestResult = - pointerEventArgs - .GetPropertyOrThrow<(IInputElement? element, IInputElement? firstEnabledAncestor)>( - "InputHitTestResult"); + pointerEventArgs.GetInputHitTestResult(); Update(pointerEventArgs.Root, inputHitTestResult.element as Visual); break; case RawPointerEventType.LeaveWindow @@ -96,12 +95,12 @@ internal sealed class ToolTipService : IDisposable public void Update(IInputRoot root, Visual? candidateToolTipHost) { var currentToolTip = _tipControl?.GetValue(ToolTip.ToolTipProperty); - if (root is SceneLayer || root is PopupShadowLayer) + if (root is SceneLayer || root is PopupRoot) { return; } - if (root == currentToolTip?.GetVisualRoot()) + if (root == currentToolTip?.PopupHost?.HostedVisualTreeRoot) { // Don't update while the pointer is over a tooltip return; @@ -109,9 +108,9 @@ internal sealed class ToolTipService : IDisposable while (candidateToolTipHost != null) { - if (candidateToolTipHost == - currentToolTip) // when OverlayPopupHost is in use, the tooltip is in the same window as the host control + if (candidateToolTipHost == currentToolTip) { + // when OverlayPopupHost is in use, the tooltip is in the same window as the host control return; } @@ -143,7 +142,7 @@ internal sealed class ToolTipService : IDisposable _tipControl = newControl; } - private void ServiceEnabledChanged(AvaloniaPropertyChangedEventArgs args) + private void HandleServiceEnabledChanged(AvaloniaPropertyChangedEventArgs args) { if (args.Sender == _tipControl && !ToolTip.GetServiceEnabled(_tipControl)) { @@ -155,7 +154,7 @@ internal sealed class ToolTipService : IDisposable /// called when the property changes on a control. /// /// The event args. - private void TipChanged(AvaloniaPropertyChangedEventArgs e) + private void HandleTipChanged(AvaloniaPropertyChangedEventArgs e) { var control = (Control)e.Sender; @@ -215,17 +214,17 @@ internal sealed class ToolTipService : IDisposable } } - private void ToolTipClosed(object? sender, EventArgs e) + private void HandleToolTipClosed(object? sender, EventArgs e) { _lastTipCloseTime = DateTime.UtcNow.Ticks; if (sender is ToolTip toolTip) { - toolTip.Closed -= ToolTipClosed; - toolTip.PointerExited -= ToolTipPointerExited; + toolTip.Closed -= HandleToolTipClosed; + toolTip.PointerExited -= HandleToolTipPointerExited; } } - private void ToolTipPointerExited(object? sender, PointerEventArgs e) + private void HandleToolTipPointerExited(object? sender, PointerEventArgs e) { // The pointer has exited the tooltip. Close the tooltip unless the current tooltip source is still the // adorned control. @@ -257,8 +256,8 @@ internal sealed class ToolTipService : IDisposable if (control.GetValue(ToolTip.ToolTipProperty) is { } tooltip) { - tooltip.Closed += ToolTipClosed; - tooltip.PointerExited += ToolTipPointerExited; + tooltip.Closed += HandleToolTipClosed; + tooltip.PointerExited += HandleToolTipPointerExited; } } } diff --git a/src/AtomUI.Controls/Tooltip/ToolTipTheme.cs b/src/AtomUI.Controls/Tooltip/ToolTipTheme.cs index 30bb56cf0..3b7421edb 100644 --- a/src/AtomUI.Controls/Tooltip/ToolTipTheme.cs +++ b/src/AtomUI.Controls/Tooltip/ToolTipTheme.cs @@ -1,11 +1,10 @@ -using AtomUI.Data; -using AtomUI.Theme; +using AtomUI.Theme; using AtomUI.Theme.Styling; -using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Layout; -using Avalonia.Media; using Avalonia.Styling; namespace AtomUI.Controls; @@ -28,21 +27,25 @@ internal class ToolTipTheme : BaseControlTheme { Name = ToolTipContainerPart }; - if (tip.Content is string text) - { - arrowDecoratedBox.Content = new SingleLineText() - { - Text = text, - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - }; - } - else if (tip.Content is Control control) - { - arrowDecoratedBox.Content = control; - } - BindUtils.RelayBind(tip, ToolTip.IsShowArrowEffectiveProperty, arrowDecoratedBox, ArrowDecoratedBox.IsShowArrowProperty); + CreateTemplateParentBinding(arrowDecoratedBox, ArrowDecoratedBox.ContentProperty, ToolTip.ContentProperty, BindingPriority.Template, + BindingMode.Default, new FuncValueConverter(o => + { + if (o is string text) + { + return new SingleLineText + { + Text = text, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + LineHeight = double.NaN, // 防止有继承属性干扰 + }; + } + + return o; + })); + CreateTemplateParentBinding(arrowDecoratedBox, ArrowDecoratedBox.ContentTemplateProperty, ToolTip.ContentTemplateProperty); + arrowDecoratedBox.RegisterInNameScope(scope); return arrowDecoratedBox; }); @@ -50,10 +53,9 @@ internal class ToolTipTheme : BaseControlTheme protected override void BuildStyles() { - this.Add(ToolTip.ShadowsProperty, ToolTipTokenKey.ToolTipShadows); - this.Add(ToolTip.DefaultMarginToAnchorProperty, ToolTipTokenKey.MarginToAnchor); - this.Add(ToolTip.MotionDurationProperty, ToolTipTokenKey.ToolTipMotionDuration); - this.Add(TemplatedControl.BackgroundProperty, SharedTokenKey.ColorTransparent); + // this.Add(ToolTipX.ShadowsProperty, ToolTipTokenKey.ToolTipShadows); + // this.Add(ToolTipX.DefaultMarginToAnchorProperty, ToolTipTokenKey.MarginToAnchor); + // this.Add(ToolTipX.MotionDurationProperty, ToolTipTokenKey.ToolTipMotionDuration); var arrowDecoratedBoxStyle = new Style(selector => selector.Nesting().Template().OfType()); arrowDecoratedBoxStyle.Add(TemplatedControl.FontSizeProperty, SharedTokenKey.FontSize); diff --git a/src/AtomUI.Controls/Tooltip/ToolTipToken.cs b/src/AtomUI.Controls/Tooltip/ToolTipToken.cs index 3db306b7d..ea9f18b72 100644 --- a/src/AtomUI.Controls/Tooltip/ToolTipToken.cs +++ b/src/AtomUI.Controls/Tooltip/ToolTipToken.cs @@ -49,11 +49,6 @@ internal class ToolTipToken : AbstractControlDesignToken /// public TimeSpan ToolTipMotionDuration { get; set; } - /// - /// 默认距离锚控件的间距 - /// - public double MarginToAnchor { get; set; } - /// /// ToolTip 箭头三角形大小 /// @@ -73,6 +68,5 @@ internal class ToolTipToken : AbstractControlDesignToken ToolTipPadding = new Thickness(SharedToken.PaddingSM, SharedToken.PaddingSM / 2 + 2); ToolTipShadows = SharedToken.BoxShadowsSecondary; ToolTipMotionDuration = SharedToken.MotionDurationMid; - MarginToAnchor = SharedToken.MarginXXS / 2; } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Utils/RawPointerEventTypeReflectionExtensions.cs b/src/AtomUI.Controls/Utils/RawPointerEventTypeReflectionExtensions.cs new file mode 100644 index 000000000..d48a81b7d --- /dev/null +++ b/src/AtomUI.Controls/Utils/RawPointerEventTypeReflectionExtensions.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using AtomUI.Reflection; +using Avalonia.Input; +using Avalonia.Input.Raw; + +namespace AtomUI.Controls.Utils; + +internal static class RawPointerEventTypeReflectionExtensions +{ + #region 反射信息定义 + private static readonly Lazy InputHitTestResultPropertyInfo = new Lazy( + typeof(RawPointerEventArgs).GetPropertyInfoOrThrow("InputHitTestResult", + BindingFlags.Instance | BindingFlags.NonPublic)); + #endregion + + public static (IInputElement? element, IInputElement? firstEnabledAncestor) GetInputHitTestResult(this RawPointerEventArgs rawPointerEventArgs) + { + return rawPointerEventArgs.GetPropertyOrThrow<(IInputElement?, IInputElement?)>(InputHitTestResultPropertyInfo.Value); + } +} \ No newline at end of file