mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-17 05:31:01 +08:00
Add motion support for ContextMenu
This commit is contained in:
@@ -34,29 +34,37 @@ public class ContextMenu : AvaloniaContextMenu,
|
||||
string IControlSharedTokenResourcesHost.TokenId => MenuToken.ID;
|
||||
|
||||
#endregion
|
||||
|
||||
private Popup? _popup;
|
||||
|
||||
public ContextMenu()
|
||||
{
|
||||
this.RegisterResources();
|
||||
this.BindMotionProperties();
|
||||
// 我们在这里有一次初始化的机会
|
||||
var popup = new Popup
|
||||
_popup = new Popup
|
||||
{
|
||||
WindowManagerAddShadowHint = false,
|
||||
IsLightDismissEnabled = true,
|
||||
OverlayDismissEventPassThrough = true
|
||||
IsLightDismissEnabled = false,
|
||||
OverlayDismissEventPassThrough = true,
|
||||
IsDetectMouseClickEnabled = true
|
||||
};
|
||||
|
||||
popup.Opened += this.CreateEventHandler("PopupOpened");
|
||||
popup.Closed += this.CreateEventHandler<EventArgs>("PopupClosed");
|
||||
|
||||
_popup.Opened += this.CreateEventHandler("PopupOpened");
|
||||
_popup.Closed += this.CreateEventHandler<EventArgs>("PopupClosed");
|
||||
|
||||
popup.AddClosingEventHandler(this.CreateEventHandler<CancelEventArgs>("PopupClosing")!);
|
||||
popup.KeyUp += this.CreateEventHandler<KeyEventArgs>("PopupKeyUp");
|
||||
_popup.AddClosingEventHandler(this.CreateEventHandler<CancelEventArgs>("PopupClosing")!);
|
||||
_popup.KeyUp += this.CreateEventHandler<KeyEventArgs>("PopupKeyUp");
|
||||
Closing += (sender, args) =>
|
||||
{
|
||||
args.Cancel = true;
|
||||
};
|
||||
this.SetPopup(popup);
|
||||
this.SetPopup(_popup);
|
||||
Opened += (sender, args) =>
|
||||
{
|
||||
_popup.SetIgnoreIsOpenChanged(true);
|
||||
_popup.IsMotionAwareOpen = true;
|
||||
};
|
||||
}
|
||||
|
||||
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
|
||||
@@ -68,4 +76,21 @@ public class ContextMenu : AvaloniaContextMenu,
|
||||
|
||||
base.PrepareContainerForItemOverride(container, item, index);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_popup?.SetIgnoreIsOpenChanged(true);
|
||||
base.Close();
|
||||
if (_popup != null)
|
||||
{
|
||||
foreach (var childItem in Items)
|
||||
{
|
||||
if (childItem is MenuItem menuItem)
|
||||
{
|
||||
menuItem.IsSubMenuOpen = false;
|
||||
}
|
||||
}
|
||||
_popup.IsMotionAwareOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@
|
||||
WindowManagerAddShadowHint="False"
|
||||
IsLightDismissEnabled="False"
|
||||
Placement="RightEdgeAlignedTop"
|
||||
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
IsMotionAwareOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
<Border Name="{x:Static atom:MenuItemThemeConstants.PopupFramePart}">
|
||||
<atom:MenuScrollViewer IsScrollChainingEnabled="False">
|
||||
<ItemsPresenter Name="{x:Static atom:MenuItemThemeConstants.ItemsPresenterPart}"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
WindowManagerAddShadowHint="False"
|
||||
IsLightDismissEnabled="False"
|
||||
Placement="BottomEdgeAlignedLeft"
|
||||
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
IsMotionAwareOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
<Border Name="{x:Static atom:TopLevelMenuItemThemeConstants.PopupFramePart}">
|
||||
<atom:MenuScrollViewer
|
||||
IsScrollChainingEnabled="False">
|
||||
|
||||
@@ -206,7 +206,7 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
}
|
||||
|
||||
_currentPressedIsValid = false;
|
||||
if (e.InitialPressMouseButton == MouseButton.Left && item?.HasSubMenu == false)
|
||||
if (e.InitialPressMouseButton == MouseButton.Left && item.HasSubMenu == false)
|
||||
{
|
||||
Click(item);
|
||||
e.Handled = true;
|
||||
|
||||
@@ -23,8 +23,11 @@ public abstract class NavMenuBase : SelectingItemsControl,
|
||||
nameof(IsOpen),
|
||||
o => o.IsOpen);
|
||||
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty
|
||||
= MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<NavMenuBase>();
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
|
||||
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<NavMenuBase>();
|
||||
|
||||
public static readonly StyledProperty<bool> IsAccordionModeProperty =
|
||||
AvaloniaProperty.Register<NavMenu, bool>(nameof(IsAccordionMode), false);
|
||||
|
||||
private bool _isOpen;
|
||||
|
||||
@@ -40,6 +43,12 @@ public abstract class NavMenuBase : SelectingItemsControl,
|
||||
set => SetValue(IsMotionEnabledProperty, value);
|
||||
}
|
||||
|
||||
public bool IsAccordionMode
|
||||
{
|
||||
get => GetValue(IsAccordionModeProperty);
|
||||
set => SetValue(IsAccordionModeProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共事件定义
|
||||
@@ -106,6 +115,11 @@ public abstract class NavMenuBase : SelectingItemsControl,
|
||||
/// </summary>
|
||||
protected internal INavMenuInteractionHandler? InteractionHandler { get; protected set; }
|
||||
|
||||
static NavMenuBase()
|
||||
{
|
||||
NavMenuItem.SubmenuOpenedEvent.AddClassHandler<NavMenuBase>((x, e) => x.OnSubmenuOpened(e));
|
||||
}
|
||||
|
||||
public NavMenuBase()
|
||||
{
|
||||
this.RegisterResources();
|
||||
@@ -151,17 +165,34 @@ public abstract class NavMenuBase : SelectingItemsControl,
|
||||
/// <param name="e">The event args.</param>
|
||||
protected virtual void OnSubmenuOpened(RoutedEventArgs e)
|
||||
{
|
||||
if (e.Source is NavMenuItem menuItem && menuItem.Parent == this)
|
||||
if (IsAccordionMode)
|
||||
{
|
||||
foreach (var child in this.GetLogicalChildren().OfType<NavMenuItem>())
|
||||
if (e.Source is NavMenuItem menuItem && menuItem.Parent == this)
|
||||
{
|
||||
if (child != menuItem && child.IsSubMenuOpen)
|
||||
foreach (var child in this.GetLogicalChildren().OfType<NavMenuItem>())
|
||||
{
|
||||
if (child != menuItem && child.IsSubMenuOpen)
|
||||
{
|
||||
child.IsSubMenuOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
IsOpen = true;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == IsAccordionModeProperty)
|
||||
{
|
||||
if (change.GetNewValue<bool>())
|
||||
{
|
||||
foreach (var child in this.GetLogicalChildren().OfType<NavMenuItem>())
|
||||
{
|
||||
child.IsSubMenuOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsOpen = true;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using Point = Avalonia.Point;
|
||||
|
||||
@@ -529,7 +530,6 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetCurrentValue(IsSubMenuOpenProperty, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
WindowManagerAddShadowHint="False"
|
||||
IsLightDismissEnabled="False"
|
||||
Placement="RightEdgeAlignedTop"
|
||||
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
IsMotionAwareOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
<Border Name="{x:Static atom:NavMenuItemThemeConstants.PopupFramePart}">
|
||||
<atom:MenuScrollViewer>
|
||||
<ItemsPresenter Name="{x:Static atom:NavMenuItemThemeConstants.ItemsPresenterPart}"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
WindowManagerAddShadowHint="False"
|
||||
IsLightDismissEnabled="False"
|
||||
Placement="BottomEdgeAlignedLeft"
|
||||
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
IsMotionAwareOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
|
||||
<Border Name="{x:Static atom:TopLevelHorizontalNavMenuItemThemeConstants.PopupFramePart}"
|
||||
MinWidth="{TemplateBinding EffectivePopupMinWidth}">
|
||||
<atom:MenuScrollViewer>
|
||||
|
||||
@@ -56,6 +56,12 @@ public class Popup : AvaloniaPopup,
|
||||
|
||||
public static readonly StyledProperty<bool> IsWaveSpiritEnabledProperty =
|
||||
WaveSpiritAwareControlProperty.IsWaveSpiritEnabledProperty.AddOwner<Popup>();
|
||||
|
||||
public static readonly StyledProperty<bool> IsMotionAwareOpenProperty =
|
||||
AvaloniaProperty.Register<Popup, bool>(nameof (IsMotionAwareOpen));
|
||||
|
||||
public static readonly StyledProperty<bool> ConfigureBlankMaskWhenMotionAwareOpenProperty =
|
||||
AvaloniaProperty.Register<Popup, bool>(nameof (ConfigureBlankMaskWhenMotionAwareOpen), false);
|
||||
|
||||
public BoxShadows MaskShadows
|
||||
{
|
||||
@@ -106,6 +112,22 @@ public class Popup : AvaloniaPopup,
|
||||
get => GetValue(CloseMotionProperty);
|
||||
set => SetValue(CloseMotionProperty, value);
|
||||
}
|
||||
|
||||
public bool IsMotionAwareOpen
|
||||
{
|
||||
get => GetValue(IsMotionAwareOpenProperty);
|
||||
set => SetValue(IsMotionAwareOpenProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the animation is turned on and off, whether to not make a complete mask after the animation ends, including the content
|
||||
/// </summary>
|
||||
public bool ConfigureBlankMaskWhenMotionAwareOpen
|
||||
{
|
||||
get => GetValue(ConfigureBlankMaskWhenMotionAwareOpenProperty);
|
||||
set => SetValue(ConfigureBlankMaskWhenMotionAwareOpenProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部属性定义
|
||||
@@ -134,6 +156,7 @@ public class Popup : AvaloniaPopup,
|
||||
private IDisposable? _selfLightDismissDisposable;
|
||||
private IManagedPopupPositionerPopup? _managedPopupPositioner;
|
||||
private bool _isNeedDetectFlip = true;
|
||||
private bool _ignoreIsOpenChanged;
|
||||
|
||||
// 在翻转之后或者恢复正常,会有属性的变动,在变动之后捕捉动画需要等一个事件循环,保证布局已经生效
|
||||
private bool _isNeedWaitFlipSync;
|
||||
@@ -246,7 +269,7 @@ public class Popup : AvaloniaPopup,
|
||||
|
||||
private void HandleMouseClick(RawInputEventArgs args)
|
||||
{
|
||||
if (!IsOpen)
|
||||
if (!IsMotionAwareOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -257,12 +280,15 @@ public class Popup : AvaloniaPopup,
|
||||
{
|
||||
if (pointerEventArgs.Type == RawPointerEventType.LeftButtonUp)
|
||||
{
|
||||
if (_firstDetected)
|
||||
if (_firstDetected || _openAnimating)
|
||||
{
|
||||
_firstDetected = false;
|
||||
if (_firstDetected)
|
||||
{
|
||||
_firstDetected = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this is IPopupHostProvider popupHostProvider)
|
||||
{
|
||||
if (popupHostProvider.PopupHost != pointerEventArgs.Root)
|
||||
@@ -510,7 +536,7 @@ public class Popup : AvaloniaPopup,
|
||||
public void MotionAwareOpen(Action? opened = null)
|
||||
{
|
||||
// AbstractPopup is currently open
|
||||
if (IsOpen || _openAnimating || _closeAnimating)
|
||||
if (_openAnimating || _closeAnimating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -520,6 +546,11 @@ public class Popup : AvaloniaPopup,
|
||||
Open();
|
||||
opened?.Invoke();
|
||||
(Host as PopupRoot)?.PlatformImpl?.SetTopmost(true);
|
||||
using (BeginIgnoringIsOpen())
|
||||
{
|
||||
SetCurrentValue(IsMotionAwareOpenProperty, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -551,6 +582,10 @@ public class Popup : AvaloniaPopup,
|
||||
}, () =>
|
||||
{
|
||||
opened?.Invoke();
|
||||
using (BeginIgnoringIsOpen())
|
||||
{
|
||||
SetCurrentValue(IsMotionAwareOpenProperty, true);
|
||||
}
|
||||
popupRoot.PlatformImpl?.SetTopmost(true);
|
||||
_isNeedWaitFlipSync = false;
|
||||
_openAnimating = false;
|
||||
@@ -559,11 +594,7 @@ public class Popup : AvaloniaPopup,
|
||||
RequestCloseWhereAnimationCompleted = false;
|
||||
Dispatcher.UIThread.Post(() => { MotionAwareClose(); });
|
||||
}
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
|
||||
popupRoot.Opacity = 1.0d;
|
||||
});
|
||||
Dispatcher.UIThread.Post(() => { popupRoot.Opacity = 1.0d; });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -580,16 +611,16 @@ public class Popup : AvaloniaPopup,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Debug.Assert(_buddyLayer != null);
|
||||
if (!IsMotionEnabled)
|
||||
{
|
||||
_isNeedDetectFlip = true;
|
||||
Close();
|
||||
closed?.Invoke();
|
||||
using (BeginIgnoringIsOpen())
|
||||
{
|
||||
SetCurrentValue(IsMotionAwareOpenProperty, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -603,6 +634,10 @@ public class Popup : AvaloniaPopup,
|
||||
closed?.Invoke();
|
||||
_closeAnimating = false;
|
||||
Close();
|
||||
using (BeginIgnoringIsOpen())
|
||||
{
|
||||
SetCurrentValue(IsMotionAwareOpenProperty, false);
|
||||
}
|
||||
_isNeedDetectFlip = true;
|
||||
});
|
||||
}
|
||||
@@ -614,6 +649,35 @@ public class Popup : AvaloniaPopup,
|
||||
{
|
||||
_isNeedWaitFlipSync = true;
|
||||
}
|
||||
else if (change.Property == IsMotionAwareOpenProperty)
|
||||
{
|
||||
if (!_ignoreIsOpenChanged)
|
||||
{
|
||||
if (change.GetNewValue<bool>())
|
||||
{
|
||||
MotionAwareOpen();
|
||||
}
|
||||
else
|
||||
{
|
||||
MotionAwareClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IgnoreIsOpenScope BeginIgnoringIsOpen() => new IgnoreIsOpenScope(this);
|
||||
|
||||
private readonly struct IgnoreIsOpenScope : IDisposable
|
||||
{
|
||||
private readonly Popup _owner;
|
||||
|
||||
public IgnoreIsOpenScope(Popup owner)
|
||||
{
|
||||
_owner = owner;
|
||||
_owner._ignoreIsOpenChanged = true;
|
||||
}
|
||||
|
||||
public void Dispose() => _owner._ignoreIsOpenChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,4 +689,4 @@ public class PopupFlippedEventArgs : EventArgs
|
||||
{
|
||||
Flipped = flipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,14 @@ internal class PopupBuddyDecorator : SceneMotionActorControl
|
||||
if (sender is ContentPresenter presenter)
|
||||
{
|
||||
ConfigureDecorator(presenter);
|
||||
ConfigureBlankContentControl();
|
||||
if (_popup.ConfigureBlankMaskWhenMotionAwareOpen)
|
||||
{
|
||||
ConfigureBlankContentControl();
|
||||
}
|
||||
else
|
||||
{
|
||||
CaptureContentControl();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ internal class PopupBuddyLayer : SceneLayer, IPopupBuddyLayer, IShadowAwareLayer
|
||||
public void Attach()
|
||||
{
|
||||
SetupPopupHost();
|
||||
_buddyDecorator.ConfigureBlankContentControl();
|
||||
_buddyDecorator.CaptureContentControl();
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
PlatformImpl?.SetTopmost(true);
|
||||
@@ -211,8 +211,7 @@ internal class PopupBuddyLayer : SceneLayer, IPopupBuddyLayer, IShadowAwareLayer
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachWithMotion(Action? aboutToStart = null,
|
||||
Action? completedAction = null)
|
||||
public void AttachWithMotion(Action? aboutToStart = null, Action? completedAction = null)
|
||||
{
|
||||
_buddyDecorator.CaptureContentControl();
|
||||
_buddyDecorator.Opacity = 0.0d;
|
||||
@@ -227,13 +226,15 @@ internal class PopupBuddyLayer : SceneLayer, IPopupBuddyLayer, IShadowAwareLayer
|
||||
motion.Run(_buddyDecorator, null, () =>
|
||||
{
|
||||
completedAction?.Invoke();
|
||||
_buddyDecorator.ConfigureBlankContentControl();
|
||||
if (_buddyPopup.ConfigureBlankMaskWhenMotionAwareOpen)
|
||||
{
|
||||
_buddyDecorator.ConfigureBlankContentControl();
|
||||
}
|
||||
NotifyAttachMotionCompleted();
|
||||
});
|
||||
}
|
||||
|
||||
public void DetachWithMotion(Action? aboutToStart = null,
|
||||
Action? completedAction = null)
|
||||
public void DetachWithMotion(Action? aboutToStart = null, Action? completedAction = null)
|
||||
{
|
||||
var motion = CloseMotion ?? new ZoomBigOutMotion();
|
||||
if (MotionDuration != TimeSpan.Zero)
|
||||
|
||||
Reference in New Issue
Block a user