mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-27 12:17:42 +08:00
Refactor Tooltip
This commit is contained in:
@@ -9,8 +9,6 @@ namespace AtomUI.MotionScene;
|
||||
|
||||
public class AbstractMotion : IMotion
|
||||
{
|
||||
public bool IsRunning { get; }
|
||||
|
||||
public RelativePoint RenderTransformOrigin { get; protected set; }
|
||||
public IList<Animation> 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)
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace AtomUI.MotionScene;
|
||||
|
||||
internal interface IMotion
|
||||
{
|
||||
public bool IsRunning { get; }
|
||||
public RelativePoint RenderTransformOrigin { get; }
|
||||
public IList<Animation> Animations { get; }
|
||||
public TimeSpan Duration { get; }
|
||||
|
||||
@@ -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) =>
|
||||
{
|
||||
|
||||
@@ -53,6 +53,16 @@ public static class ObjectExtension
|
||||
return false;
|
||||
}
|
||||
|
||||
public static T? GetPropertyOrThrow<T>(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<T>(this object source, string name,
|
||||
BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic |
|
||||
BindingFlags.Public | BindingFlags.FlattenHierarchy)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using AtomUI.Reflection;
|
||||
using Avalonia;
|
||||
|
||||
namespace AtomUI.Utils;
|
||||
|
||||
// 反射扩展类
|
||||
internal static class AvaloniaPropertyReflectionExtensions
|
||||
{
|
||||
public static void NotifyChanged<TValue>(this AvaloniaProperty<TValue> property, AvaloniaPropertyChangedEventArgs<TValue> e)
|
||||
{
|
||||
var methodInfo = typeof(AvaloniaProperty<TValue>).GetMethodInfoOrThrow("NotifyChanged");
|
||||
methodInfo.Invoke(property, [e]);
|
||||
}
|
||||
}
|
||||
@@ -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<ArrowDecoratedBox>(IsShowArrowProperty, ArrowDirectionProperty);
|
||||
AffectsArrange<ArrowDecoratedBox>(ArrowPositionProperty);
|
||||
AffectsRender<ArrowDecoratedBox>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 公共属性定义
|
||||
|
||||
/// <summary>
|
||||
/// 距离 anchor 的边距,根据垂直和水平进行设置
|
||||
/// 但是对某些组合无效,比如跟随鼠标的情况
|
||||
/// 还有些 anchor 和 gravity 的组合也没有用
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> MarginToAnchorProperty =
|
||||
/// 距离 anchor 的边距,根据垂直和水平进行设置
|
||||
/// 但是对某些组合无效,比如跟随鼠标的情况
|
||||
/// 还有些 anchor 和 gravity 的组合也没有用
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> MarginToAnchorProperty =
|
||||
Popup.MarginToAnchorProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <inheritdoc cref="Popup.PlacementProperty" />
|
||||
public static readonly StyledProperty<PlacementMode> PlacementProperty =
|
||||
/// <inheritdoc cref="Popup.PlacementProperty" />
|
||||
public static readonly StyledProperty<PlacementMode> PlacementProperty =
|
||||
Avalonia.Controls.Primitives.Popup.PlacementProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <inheritdoc cref="Popup.HorizontalOffsetProperty" />
|
||||
public static readonly StyledProperty<double> HorizontalOffsetProperty =
|
||||
/// <inheritdoc cref="Popup.HorizontalOffsetProperty" />
|
||||
public static readonly StyledProperty<double> HorizontalOffsetProperty =
|
||||
Avalonia.Controls.Primitives.Popup.HorizontalOffsetProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <inheritdoc cref="Popup.VerticalOffsetProperty" />
|
||||
public static readonly StyledProperty<double> VerticalOffsetProperty =
|
||||
/// <inheritdoc cref="Popup.VerticalOffsetProperty" />
|
||||
public static readonly StyledProperty<double> VerticalOffsetProperty =
|
||||
Avalonia.Controls.Primitives.Popup.VerticalOffsetProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <inheritdoc cref="Popup.PlacementAnchorProperty" />
|
||||
public static readonly StyledProperty<PopupAnchor> PlacementAnchorProperty =
|
||||
/// <inheritdoc cref="Popup.PlacementAnchorProperty" />
|
||||
public static readonly StyledProperty<PopupAnchor> PlacementAnchorProperty =
|
||||
Avalonia.Controls.Primitives.Popup.PlacementAnchorProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <inheritdoc cref="Popup.PlacementAnchorProperty" />
|
||||
public static readonly StyledProperty<PopupGravity> PlacementGravityProperty =
|
||||
/// <inheritdoc cref="Popup.PlacementAnchorProperty" />
|
||||
public static readonly StyledProperty<PopupGravity> PlacementGravityProperty =
|
||||
Avalonia.Controls.Primitives.Popup.PlacementGravityProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ShowMode" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<FlyoutShowMode> ShowModeProperty =
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ShowMode" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<FlyoutShowMode> ShowModeProperty =
|
||||
AvaloniaProperty.Register<PopupFlyoutBase, FlyoutShowMode>(nameof(ShowMode));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="OverlayInputPassThroughElement" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty =
|
||||
/// <summary>
|
||||
/// Defines the <see cref="OverlayInputPassThroughElement" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty =
|
||||
Avalonia.Controls.Primitives.Popup.OverlayInputPassThroughElementProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="PlacementConstraintAdjustment" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<PopupPositionerConstraintAdjustment> PlacementConstraintAdjustmentProperty =
|
||||
/// <summary>
|
||||
/// Defines the <see cref="PlacementConstraintAdjustment" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<PopupPositionerConstraintAdjustment> PlacementConstraintAdjustmentProperty =
|
||||
Avalonia.Controls.Primitives.Popup.PlacementConstraintAdjustmentProperty.AddOwner<PopupFlyoutBase>();
|
||||
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty
|
||||
= AvaloniaProperty.Register<PopupFlyoutBase, bool>(nameof(IsMotionEnabled));
|
||||
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty
|
||||
= AvaloniaProperty.Register<PopupFlyoutBase, bool>(nameof(IsMotionEnabled));
|
||||
|
||||
public double MarginToAnchor
|
||||
{
|
||||
get => GetValue(MarginToAnchorProperty);
|
||||
set => SetValue(MarginToAnchorProperty, value);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="Popup.Placement" />
|
||||
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<bool> IsDetectMouseClickEnabledProperty =
|
||||
AvaloniaProperty.Register<Flyout, bool>(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<CancelEventArgs>(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
|
||||
|
||||
-1
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<MethodInfo> SetPopupParentMethodInfo = new Lazy<MethodInfo>(
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@@ -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<PopupRoot, EventHandler<PixelPointEventArgs>>(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<Popup, EventHandler>(_target, HandleTargetOpened,
|
||||
(x, handler) => x.Opened += handler,
|
||||
(x, handler) => x.Opened -= handler).DisposeWith(handlerCleanup);
|
||||
SubscribeToEventHandler<Popup, EventHandler<EventArgs>>(_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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<IInputManager>()!;
|
||||
_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<bool> args)
|
||||
private void HandleServiceEnabledChanged(AvaloniaPropertyChangedEventArgs<bool> args)
|
||||
{
|
||||
if (args.Sender == _tipControl && !ToolTip.GetServiceEnabled(_tipControl))
|
||||
{
|
||||
@@ -155,7 +154,7 @@ internal sealed class ToolTipService : IDisposable
|
||||
/// called when the <see cref="ToolTip.TipProperty" /> property changes on a control.
|
||||
/// </summary>
|
||||
/// <param name="e">The event args.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<object?, object?>(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<ArrowDecoratedBox>());
|
||||
arrowDecoratedBoxStyle.Add(TemplatedControl.FontSizeProperty, SharedTokenKey.FontSize);
|
||||
|
||||
@@ -49,11 +49,6 @@ internal class ToolTipToken : AbstractControlDesignToken
|
||||
/// </summary>
|
||||
public TimeSpan ToolTipMotionDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认距离锚控件的间距
|
||||
/// </summary>
|
||||
public double MarginToAnchor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ToolTip 箭头三角形大小
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<PropertyInfo> InputHitTestResultPropertyInfo = new Lazy<PropertyInfo>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user