using System.Reactive.Disposables; using System.Windows.Input; using AtomUI.Controls.Themes; using AtomUI.Controls.Utils; using AtomUI.Data; using AtomUI.IconPkg; using AtomUI.IconPkg.AntDesign; using AtomUI.Input; using AtomUI.Theme; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.VisualTree; namespace AtomUI.Controls; public class SplitButton : ContentControl, ICommandSource, ISizeTypeAware, IControlSharedTokenResourcesHost, IWaveSpiritAwareControl { #region 公共属性定义 public static readonly RoutedEvent ClickEvent = RoutedEvent.Register( nameof(Click), RoutingStrategies.Bubble); public static readonly StyledProperty CommandProperty = Avalonia.Controls.Button.CommandProperty.AddOwner(); public static readonly StyledProperty CommandParameterProperty = Avalonia.Controls.Button.CommandParameterProperty.AddOwner(); public static readonly StyledProperty FlyoutProperty = AvaloniaProperty.Register(nameof(Flyout)); public static readonly StyledProperty HotKeyProperty = Avalonia.Controls.Button.HotKeyProperty.AddOwner(); public static readonly StyledProperty TriggerTypeProperty = FlyoutStateHelper.TriggerTypeProperty.AddOwner(); public static readonly StyledProperty IsShowArrowProperty = ArrowDecoratedBox.IsShowArrowProperty.AddOwner(); public static readonly StyledProperty IsPointAtCenterProperty = Flyout.IsPointAtCenterProperty.AddOwner(); public static readonly StyledProperty PlacementProperty = Avalonia.Controls.Primitives.Popup.PlacementProperty.AddOwner(); public static readonly StyledProperty PlacementAnchorProperty = Avalonia.Controls.Primitives.Popup.PlacementAnchorProperty.AddOwner(); public static readonly StyledProperty PlacementGravityProperty = Avalonia.Controls.Primitives.Popup.PlacementGravityProperty.AddOwner(); public static readonly StyledProperty MarginToAnchorProperty = Popup.MarginToAnchorProperty.AddOwner(); public static readonly StyledProperty MouseEnterDelayProperty = FlyoutStateHelper.MouseEnterDelayProperty.AddOwner(); public static readonly StyledProperty MouseLeaveDelayProperty = FlyoutStateHelper.MouseLeaveDelayProperty.AddOwner(); public static readonly StyledProperty IsShowIndicatorProperty = AvaloniaProperty.Register(nameof(IsShowIndicator), true); public static readonly StyledProperty SizeTypeProperty = SizeTypeAwareControlProperty.SizeTypeProperty.AddOwner(); public static readonly StyledProperty IconProperty = Button.IconProperty.AddOwner(); public static readonly StyledProperty FlyoutButtonIconProperty = AvaloniaProperty.Register(nameof(FlyoutButtonIcon)); public static readonly StyledProperty IsDangerProperty = Button.IsDangerProperty.AddOwner(); public static readonly StyledProperty IsPrimaryButtonTypeProperty = AvaloniaProperty.Register(nameof(IsPrimaryButtonType)); public static readonly StyledProperty IsMotionEnabledProperty = MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); public static readonly StyledProperty IsWaveSpiritEnabledProperty = WaveSpiritAwareControlProperty.IsWaveSpiritEnabledProperty.AddOwner(); public event EventHandler? Click { add => AddHandler(ClickEvent, value); remove => RemoveHandler(ClickEvent, value); } public ICommand? Command { get => GetValue(CommandProperty); set => SetValue(CommandProperty, value); } /// /// Gets or sets a parameter to be passed to the . /// public object? CommandParameter { get => GetValue(CommandParameterProperty); set => SetValue(CommandParameterProperty, value); } /// /// Gets or sets the that is shown when the secondary part is pressed. /// public Flyout? Flyout { get => GetValue(FlyoutProperty); set => SetValue(FlyoutProperty, value); } /// /// Gets or sets an associated with this control /// public KeyGesture? HotKey { get => GetValue(HotKeyProperty); set => SetValue(HotKeyProperty, value); } public FlyoutTriggerType TriggerType { get => GetValue(TriggerTypeProperty); set => SetValue(TriggerTypeProperty, value); } public bool IsShowArrow { get => GetValue(IsShowArrowProperty); set => SetValue(IsShowArrowProperty, value); } public bool IsPointAtCenter { get => GetValue(IsPointAtCenterProperty); set => SetValue(IsPointAtCenterProperty, value); } public PlacementMode Placement { get => GetValue(PlacementProperty); set => SetValue(PlacementProperty, value); } public PopupGravity PlacementGravity { get => GetValue(PlacementGravityProperty); set => SetValue(PlacementGravityProperty, value); } public PopupAnchor PlacementAnchor { get => GetValue(PlacementAnchorProperty); set => SetValue(PlacementAnchorProperty, value); } public double MarginToAnchor { get => GetValue(MarginToAnchorProperty); set => SetValue(MarginToAnchorProperty, value); } public int MouseEnterDelay { get => GetValue(MouseEnterDelayProperty); set => SetValue(MouseEnterDelayProperty, value); } public int MouseLeaveDelay { get => GetValue(MouseLeaveDelayProperty); set => SetValue(MouseLeaveDelayProperty, value); } public bool IsShowIndicator { get => GetValue(IsShowIndicatorProperty); set => SetValue(IsShowIndicatorProperty, value); } public SizeType SizeType { get => GetValue(SizeTypeProperty); set => SetValue(SizeTypeProperty, value); } public Icon? Icon { get => GetValue(IconProperty); set => SetValue(IconProperty, value); } public Icon? FlyoutButtonIcon { get => GetValue(FlyoutButtonIconProperty); set => SetValue(FlyoutButtonIconProperty, value); } public bool IsDanger { get => GetValue(IsDangerProperty); set => SetValue(IsDangerProperty, value); } public bool IsPrimaryButtonType { get => GetValue(IsPrimaryButtonTypeProperty); set => SetValue(IsPrimaryButtonTypeProperty, value); } public bool IsMotionEnabled { get => GetValue(IsMotionEnabledProperty); set => SetValue(IsMotionEnabledProperty, value); } public bool IsWaveSpiritEnabled { get => GetValue(IsWaveSpiritEnabledProperty); set => SetValue(IsWaveSpiritEnabledProperty, value); } #endregion #region 内部属性定义 internal static readonly StyledProperty SplitSeparatorBrushProperty = AvaloniaProperty.Register(nameof (SplitSeparatorBrush)); internal static readonly DirectProperty EffectiveButtonTypeProperty = AvaloniaProperty.RegisterDirect(nameof(EffectiveButtonType), o => o.EffectiveButtonType, (o, v) => o.EffectiveButtonType = v); internal IBrush? SplitSeparatorBrush { get => GetValue(SplitSeparatorBrushProperty); set => SetValue(SplitSeparatorBrushProperty, value); } private ButtonType _effectiveButtonType; internal ButtonType EffectiveButtonType { get => _effectiveButtonType; set => SetAndRaise(EffectiveButtonTypeProperty, ref _effectiveButtonType, value); } Control IMotionAwareControl.PropertyBindTarget => this; Control IControlSharedTokenResourcesHost.HostControl => this; string IControlSharedTokenResourcesHost.TokenId => ButtonToken.ID; #endregion private Button? _primaryButton; private Button? _secondaryButton; private KeyGesture? _hotkey; private bool _commandCanExecute = true; private bool _isFlyoutOpen; private bool _isKeyboardPressed; private readonly FlyoutStateHelper _flyoutStateHelper; private CompositeDisposable? _flyoutBindingDisposables; private CompositeDisposable? _flyoutHelperBindingDisposables; private IDisposable? _borderThicknessDisposable; static SplitButton() { PlacementProperty.OverrideDefaultValue(PlacementMode.BottomEdgeAlignedRight); IsShowArrowProperty.OverrideDefaultValue(false); HorizontalAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Left); VerticalAlignmentProperty.OverrideDefaultValue(VerticalAlignment.Top); AffectsRender(IsPrimaryButtonTypeProperty, IsDangerProperty, SplitSeparatorBrushProperty); } public SplitButton() { _flyoutStateHelper = new FlyoutStateHelper(); this.RegisterResources(); } internal virtual bool InternalIsChecked => false; protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; void ICommandSource.CanExecuteChanged(object sender, EventArgs e) { CanExecuteChanged(sender, e); } private void CanExecuteChanged(object? sender, EventArgs e) { var (command, parameter) = (Command, CommandParameter); CanExecuteChanged(command, parameter); } private void CanExecuteChanged(ICommand? command, object? parameter) { if (!this.IsAttachedToLogicalTree()) { return; } var canExecute = command is null || command.CanExecute(parameter); if (canExecute != _commandCanExecute) { _commandCanExecute = canExecute; UpdateIsEffectivelyEnabled(); } } /// /// Updates the visual state of the control by applying latest PseudoClasses. /// protected void UpdatePseudoClasses() { PseudoClasses.Set(StdPseudoClass.FlyoutOpen, _isFlyoutOpen); PseudoClasses.Set(StdPseudoClass.Pressed, _isKeyboardPressed); PseudoClasses.Set(StdPseudoClass.Checked, InternalIsChecked); } protected void OpenFlyout() { _flyoutStateHelper.ShowFlyout(); } /// /// Closes the secondary button's flyout. /// protected void CloseFlyout() { _flyoutStateHelper.HideFlyout(); } /// /// Registers all flyout events. /// /// The flyout to connect events to. private void RegisterFlyoutEvents(Flyout? flyout) { if (flyout != null) { flyout.Opened += HandleFlyoutOpened; flyout.Closed += HandleFlyoutClosed; if (flyout is MenuFlyout menuFlyout) { menuFlyout.IsDetectMouseClickEnabled = false; menuFlyout.ClickHideFlyoutPredicate = ClickHideFlyoutPredicate; } _flyoutBindingDisposables?.Dispose(); _flyoutBindingDisposables = new CompositeDisposable(9); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, PlacementProperty, flyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, PlacementAnchorProperty, flyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, PlacementGravityProperty, flyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsShowArrowProperty, flyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsPointAtCenterProperty, flyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, MarginToAnchorProperty, flyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, MarginToAnchorProperty, flyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, flyout)); _flyoutBindingDisposables.Add(flyout.GetPropertyChangedObservable(Avalonia.Controls.Primitives.Popup .PlacementProperty) .Subscribe(HandleFlyoutPlacementPropertyChanged)); } } /// /// Explicitly unregisters all flyout events. /// /// The flyout to disconnect events from. private void UnregisterFlyoutEvents(Flyout? flyout) { if (flyout != null) { flyout.Opened -= HandleFlyoutOpened; flyout.Closed -= HandleFlyoutClosed; if (flyout is MenuFlyout menuFlyout) { menuFlyout.ClickHideFlyoutPredicate = null; } _flyoutBindingDisposables?.Dispose(); _flyoutBindingDisposables = null; } } /// /// Explicitly unregisters all events related to the two buttons in OnApplyTemplate(). /// private void UnregisterEvents() { if (_primaryButton != null) { _primaryButton.Click -= HandlePrimaryButtonClick; } } private void SetupDefaultFlyoutButtonIcon() { if (FlyoutButtonIcon == null) { ClearValue(FlyoutButtonIconProperty); SetValue(FlyoutButtonIconProperty, AntDesignIconPackage.EllipsisOutlined(), BindingPriority.Template); } } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); UnregisterEvents(); _primaryButton = e.NameScope.Find