mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-30 02:47:45 +08:00
完成基础的 DropdownButton
This commit is contained in:
parent
ed23296d55
commit
32b0736746
@ -11,12 +11,19 @@
|
|||||||
<showcase:ShowCaseItem
|
<showcase:ShowCaseItem
|
||||||
Title="Basic"
|
Title="Basic"
|
||||||
Description="The most basic dropdown menu.">
|
Description="The most basic dropdown menu.">
|
||||||
<atom:DropdownButton ButtonType="Link" TriggerType="Focus">
|
<atom:DropdownButton ButtonType="Link" TriggerType="Hover">
|
||||||
Hover me
|
Hover me
|
||||||
<atom:DropdownButton.DropdownFlyout>
|
<atom:DropdownButton.DropdownFlyout>
|
||||||
<atom:Flyout>
|
<atom:MenuFlyout>
|
||||||
<TextBlock Width="200" Height="100" Padding="20">The most basic example.</TextBlock>
|
<atom:MenuItem Header="Cut" InputGesture="Ctrl+X" Icon="{atom:IconProvider Kind=ScissorOutlined}" />
|
||||||
</atom:Flyout>
|
<atom:MenuItem Header="Copy" InputGesture="Ctrl+C" Icon="{atom:IconProvider Kind=CopyOutlined}" />
|
||||||
|
<atom:MenuItem Header="Delete" InputGesture="Ctrl+D" Icon="{atom:IconProvider Kind=DeleteOutlined}" />
|
||||||
|
<atom:MenuItem Header="Paste">
|
||||||
|
<atom:MenuItem Header="Paste" InputGesture="Ctrl+P"
|
||||||
|
Icon="{atom:IconProvider Kind=FileDoneOutlined}" />
|
||||||
|
<atom:MenuItem Header="Paste from History" InputGesture="Ctrl+Shift+V" />
|
||||||
|
</atom:MenuItem>
|
||||||
|
</atom:MenuFlyout>
|
||||||
</atom:DropdownButton.DropdownFlyout>
|
</atom:DropdownButton.DropdownFlyout>
|
||||||
</atom:DropdownButton>
|
</atom:DropdownButton>
|
||||||
</showcase:ShowCaseItem>
|
</showcase:ShowCaseItem>
|
||||||
|
@ -35,14 +35,6 @@
|
|||||||
</atom:FlyoutHost.Flyout>
|
</atom:FlyoutHost.Flyout>
|
||||||
<atom:Button>Hover me</atom:Button>
|
<atom:Button>Hover me</atom:Button>
|
||||||
</atom:FlyoutHost>
|
</atom:FlyoutHost>
|
||||||
<atom:FlyoutHost Trigger="Focus">
|
|
||||||
<atom:FlyoutHost.Flyout>
|
|
||||||
<atom:Flyout>
|
|
||||||
<TextBlock Width="200" Height="100" Padding="20">The most basic example.</TextBlock>
|
|
||||||
</atom:Flyout>
|
|
||||||
</atom:FlyoutHost.Flyout>
|
|
||||||
<atom:Button>Focus me</atom:Button>
|
|
||||||
</atom:FlyoutHost>
|
|
||||||
<atom:FlyoutHost Trigger="Click">
|
<atom:FlyoutHost Trigger="Click">
|
||||||
<atom:FlyoutHost.Flyout>
|
<atom:FlyoutHost.Flyout>
|
||||||
<atom:Flyout>
|
<atom:Flyout>
|
||||||
|
@ -15,14 +15,12 @@ using Avalonia.Threading;
|
|||||||
|
|
||||||
namespace AtomUI.Controls;
|
namespace AtomUI.Controls;
|
||||||
|
|
||||||
using FlyoutControl = Flyout;
|
|
||||||
|
|
||||||
public class DropdownButton : Button
|
public class DropdownButton : Button
|
||||||
{
|
{
|
||||||
#region 公共属性定义
|
#region 公共属性定义
|
||||||
|
|
||||||
public static readonly StyledProperty<FlyoutControl?> DropdownFlyoutProperty =
|
public static readonly StyledProperty<MenuFlyout?> DropdownFlyoutProperty =
|
||||||
AvaloniaProperty.Register<DropdownButton, FlyoutControl?>(nameof(DropdownFlyout));
|
AvaloniaProperty.Register<DropdownButton, MenuFlyout?>(nameof(DropdownFlyout));
|
||||||
|
|
||||||
public static readonly StyledProperty<FlyoutTriggerType> TriggerTypeProperty =
|
public static readonly StyledProperty<FlyoutTriggerType> TriggerTypeProperty =
|
||||||
AvaloniaProperty.Register<DropdownButton, FlyoutTriggerType>(nameof(TriggerType), FlyoutTriggerType.Click);
|
AvaloniaProperty.Register<DropdownButton, FlyoutTriggerType>(nameof(TriggerType), FlyoutTriggerType.Click);
|
||||||
@ -51,7 +49,7 @@ public class DropdownButton : Button
|
|||||||
public static readonly StyledProperty<int> MouseLeaveDelayProperty =
|
public static readonly StyledProperty<int> MouseLeaveDelayProperty =
|
||||||
AvaloniaProperty.Register<DropdownButton, int>(nameof(MouseLeaveDelay), 100);
|
AvaloniaProperty.Register<DropdownButton, int>(nameof(MouseLeaveDelay), 100);
|
||||||
|
|
||||||
public FlyoutControl? DropdownFlyout
|
public MenuFlyout? DropdownFlyout
|
||||||
{
|
{
|
||||||
get => GetValue(DropdownFlyoutProperty);
|
get => GetValue(DropdownFlyoutProperty);
|
||||||
set => SetValue(DropdownFlyoutProperty, value);
|
set => SetValue(DropdownFlyoutProperty, value);
|
||||||
@ -117,6 +115,7 @@ public class DropdownButton : Button
|
|||||||
private DispatcherTimer? _mouseLeaveDelayTimer;
|
private DispatcherTimer? _mouseLeaveDelayTimer;
|
||||||
private CompositeDisposable? _subscriptions;
|
private CompositeDisposable? _subscriptions;
|
||||||
private PathIcon? _openIndicatorIcon;
|
private PathIcon? _openIndicatorIcon;
|
||||||
|
private IDisposable? _flyoutCloseDetectDisposable;
|
||||||
|
|
||||||
static DropdownButton()
|
static DropdownButton()
|
||||||
{
|
{
|
||||||
@ -136,7 +135,7 @@ public class DropdownButton : Button
|
|||||||
|
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
TokenResourceBinder.CreateGlobalTokenBinding(this, MarginToAnchorProperty, GlobalTokenResourceKey.MarginXXS);
|
TokenResourceBinder.CreateGlobalTokenBinding(this, MarginToAnchorProperty, GlobalTokenResourceKey.MarginXXS);
|
||||||
SetupTriggerHandler();
|
SetupFlyoutProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
@ -162,6 +161,26 @@ public class DropdownButton : Button
|
|||||||
BindUtils.RelayBind(this, IsShowArrowProperty, DropdownFlyout);
|
BindUtils.RelayBind(this, IsShowArrowProperty, DropdownFlyout);
|
||||||
BindUtils.RelayBind(this, IsPointAtCenterProperty, DropdownFlyout);
|
BindUtils.RelayBind(this, IsPointAtCenterProperty, DropdownFlyout);
|
||||||
BindUtils.RelayBind(this, MarginToAnchorProperty, DropdownFlyout);
|
BindUtils.RelayBind(this, MarginToAnchorProperty, DropdownFlyout);
|
||||||
|
|
||||||
|
DropdownFlyout.Opened += HandleFlyoutOpened;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleFlyoutOpened(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DropdownFlyout is IPopupHostProvider popupHostProvider) {
|
||||||
|
var host = popupHostProvider.PopupHost;
|
||||||
|
if (host is PopupRoot popupRoot) {
|
||||||
|
// 这里 PopupRoot 关闭的时候会被关闭,所以这里的事件处理器是不是不需要删除
|
||||||
|
popupRoot.PointerMoved += (o, args) =>
|
||||||
|
{
|
||||||
|
StopMouseLeaveTimer();
|
||||||
|
if (_flyoutCloseDetectDisposable is null) {
|
||||||
|
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
|
||||||
|
_flyoutCloseDetectDisposable = inputManager.Process.Subscribe(DetectWhenToClosePopup);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,13 +194,6 @@ public class DropdownButton : Button
|
|||||||
HandleAnchorTargetHover(args);
|
HandleAnchorTargetHover(args);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
} else if (TriggerType == FlyoutTriggerType.Focus) {
|
|
||||||
_subscriptions.Add(IsFocusedProperty.Changed.Subscribe(args =>
|
|
||||||
{
|
|
||||||
if (args.Sender == this) {
|
|
||||||
HandleAnchorTargetFocus(args);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else if (TriggerType == FlyoutTriggerType.Click) {
|
} else if (TriggerType == FlyoutTriggerType.Click) {
|
||||||
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
|
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
|
||||||
_subscriptions.Add(inputManager.Process.Subscribe(HandleAnchorTargetClick));
|
_subscriptions.Add(inputManager.Process.Subscribe(HandleAnchorTargetClick));
|
||||||
@ -199,27 +211,40 @@ public class DropdownButton : Button
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleAnchorTargetFocus(AvaloniaPropertyChangedEventArgs<bool> e)
|
private void DetectWhenToClosePopup(RawInputEventArgs args)
|
||||||
{
|
{
|
||||||
if (DropdownFlyout is not null) {
|
if (args is RawPointerEventArgs pointerEventArgs) {
|
||||||
if (e.GetNewValue<bool>()) {
|
if (DropdownFlyout is null) {
|
||||||
if (!DropdownFlyout.IsOpen) {
|
return;
|
||||||
ShowFlyout();
|
|
||||||
}
|
}
|
||||||
} else {
|
if (DropdownFlyout.IsOpen) {
|
||||||
|
var found = false;
|
||||||
|
if (pointerEventArgs.Root is PopupRoot popupRoot) {
|
||||||
|
var current = popupRoot.Parent;
|
||||||
|
while (current is not null) {
|
||||||
|
if (current == this) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
} else if (object.Equals(pointerEventArgs.Root, this)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
HideFlyout();
|
HideFlyout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleAnchorTargetClick(RawInputEventArgs args)
|
private void HandleAnchorTargetClick(RawInputEventArgs args)
|
||||||
{
|
{
|
||||||
if (args is RawPointerEventArgs pointerEventArgs) {
|
if (args is RawPointerEventArgs pointerEventArgs) {
|
||||||
if (pointerEventArgs.Type == RawPointerEventType.LeftButtonUp) {
|
|
||||||
if (DropdownFlyout is null) {
|
if (DropdownFlyout is null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pointerEventArgs.Type == RawPointerEventType.LeftButtonUp) {
|
||||||
if (!DropdownFlyout.IsOpen) {
|
if (!DropdownFlyout.IsOpen) {
|
||||||
var pos = this.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!);
|
var pos = this.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!);
|
||||||
if (!pos.HasValue) {
|
if (!pos.HasValue) {
|
||||||
@ -246,7 +271,8 @@ public class DropdownButton : Button
|
|||||||
if (DropdownFlyout is null) {
|
if (DropdownFlyout is null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 防止干扰打开
|
||||||
|
_flyoutCloseDetectDisposable?.Dispose();
|
||||||
StopMouseEnterTimer();
|
StopMouseEnterTimer();
|
||||||
StopMouseLeaveTimer();
|
StopMouseLeaveTimer();
|
||||||
DropdownFlyout.Hide();
|
DropdownFlyout.Hide();
|
||||||
@ -262,7 +288,8 @@ public class DropdownButton : Button
|
|||||||
if (DropdownFlyout is null) {
|
if (DropdownFlyout is null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_flyoutCloseDetectDisposable?.Dispose();
|
||||||
|
_flyoutCloseDetectDisposable = null;
|
||||||
StopMouseEnterTimer();
|
StopMouseEnterTimer();
|
||||||
|
|
||||||
if (MouseLeaveDelay == 0) {
|
if (MouseLeaveDelay == 0) {
|
||||||
|
@ -18,8 +18,7 @@ using FlyoutControl = Flyout;
|
|||||||
public enum FlyoutTriggerType
|
public enum FlyoutTriggerType
|
||||||
{
|
{
|
||||||
Hover,
|
Hover,
|
||||||
Click,
|
Click
|
||||||
Focus
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FlyoutHost : Control
|
public class FlyoutHost : Control
|
||||||
@ -212,13 +211,6 @@ public class FlyoutHost : Control
|
|||||||
HandleAnchorTargetHover(args);
|
HandleAnchorTargetHover(args);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (Trigger == FlyoutTriggerType.Focus) {
|
|
||||||
_subscriptions.Add(IsFocusedProperty.Changed.Subscribe(args =>
|
|
||||||
{
|
|
||||||
if (args.Sender == AnchorTarget) {
|
|
||||||
HandleAnchorTargetFocus(args);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else if (Trigger == FlyoutTriggerType.Click) {
|
} else if (Trigger == FlyoutTriggerType.Click) {
|
||||||
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
|
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
|
||||||
_subscriptions.Add(inputManager.Process.Subscribe(HandleAnchorTargetClick));
|
_subscriptions.Add(inputManager.Process.Subscribe(HandleAnchorTargetClick));
|
||||||
@ -236,17 +228,6 @@ public class FlyoutHost : Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleAnchorTargetFocus(AvaloniaPropertyChangedEventArgs<bool> e)
|
|
||||||
{
|
|
||||||
if (Flyout is not null) {
|
|
||||||
if (e.GetNewValue<bool>()) {
|
|
||||||
ShowFlyout();
|
|
||||||
} else {
|
|
||||||
HideFlyout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleAnchorTargetClick(RawInputEventArgs args)
|
private void HandleAnchorTargetClick(RawInputEventArgs args)
|
||||||
{
|
{
|
||||||
if (args is RawPointerEventArgs pointerEventArgs) {
|
if (args is RawPointerEventArgs pointerEventArgs) {
|
||||||
|
@ -526,4 +526,19 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
|
|||||||
//Add new classes
|
//Add new classes
|
||||||
presenter.Classes.AddRange(classes);
|
presenter.Classes.AddRange(classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool InPopupRootBounds(Point position)
|
||||||
|
{
|
||||||
|
if (!IsOpen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO 后期需要加入对 Overlay 的支持
|
||||||
|
if (Popup?.Host is PopupRoot root) {
|
||||||
|
// Get the popup root bounds and convert to screen coordinates
|
||||||
|
|
||||||
|
Console.WriteLine(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user