From 08e863b4e134f08505a808de92aff3a9a2a138bc Mon Sep 17 00:00:00 2001 From: CHINBOY Date: Sat, 8 Mar 2025 17:04:02 +0800 Subject: [PATCH] Refactor ToggleButton --- src/AtomUI.Base/MotionScene/MotionInvoker.cs | 43 ----------- .../AddOnDecoratedInnerBox.cs | 11 ++- src/AtomUI.Controls/Buttons/DropdownButton.cs | 10 +-- src/AtomUI.Controls/Buttons/IconButton.cs | 4 +- src/AtomUI.Controls/Buttons/SplitButton.cs | 58 ++++++++++----- .../Buttons/ToggleIconButton.cs | 72 ++----------------- .../Buttons/ToggleIconButtonTheme.cs | 52 +++++++++++--- src/AtomUI.Controls/TreeView/TreeViewItem.cs | 6 +- 8 files changed, 104 insertions(+), 152 deletions(-) diff --git a/src/AtomUI.Base/MotionScene/MotionInvoker.cs b/src/AtomUI.Base/MotionScene/MotionInvoker.cs index 4f71d8b36..00c061cb5 100644 --- a/src/AtomUI.Base/MotionScene/MotionInvoker.cs +++ b/src/AtomUI.Base/MotionScene/MotionInvoker.cs @@ -16,49 +16,6 @@ internal static class MotionInvoker await motion.RunAsync(actor, aboutToStart, completedAction, cancellationToken); }); } - - public static void InvokeInPopupLayer(SceneMotionActorControl actor, - AbstractMotion motion, - Action? aboutToStart = null, - Action? completedAction = null, - CancellationToken cancellationToken = default) - { - var sceneLayer = PrepareSceneLayer(motion, actor); - var compositeDisposable = new CompositeDisposable(); - compositeDisposable.Add(Disposable.Create(sceneLayer, (state) => - { - sceneLayer.Hide(); - sceneLayer.Dispose(); - })); - sceneLayer.SetMotionActor(actor); - actor.NotifyMotionTargetAddedToScene(); - sceneLayer.Topmost = true; - sceneLayer.Show(); - sceneLayer.Activate(); - actor.NotifySceneShowed(); - actor.IsVisible = false; - - // 等待一个事件循环,让动画窗口置顶 - Dispatcher.UIThread.Post(() => - { - Dispatcher.UIThread.InvokeAsync(async () => - { - // 主要等待正常窗体显示出来再隐藏对话层,不然感觉会闪屏 - await motion.RunAsync(actor, aboutToStart, () => - { - if (completedAction is not null) - { - completedAction(); - } - }, cancellationToken); - DispatcherTimer.RunOnce(() => - { - compositeDisposable.Dispose(); - compositeDisposable = null; - }, TimeSpan.FromMilliseconds(500)); - }); - }); - } public static void DispatchInMotionSceneLayer(SceneMotionActorControl actor, AbstractMotion motion, diff --git a/src/AtomUI.Controls/AddOnDecoratedBox/AddOnDecoratedInnerBox.cs b/src/AtomUI.Controls/AddOnDecoratedBox/AddOnDecoratedInnerBox.cs index c96a30fd5..6d6b88886 100644 --- a/src/AtomUI.Controls/AddOnDecoratedBox/AddOnDecoratedInnerBox.cs +++ b/src/AtomUI.Controls/AddOnDecoratedBox/AddOnDecoratedInnerBox.cs @@ -164,7 +164,7 @@ public class AddOnDecoratedInnerBox : ContentControl, protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - + if (change.Property == LeftAddOnContentProperty || change.Property == RightAddOnContentProperty) { @@ -172,7 +172,7 @@ public class AddOnDecoratedInnerBox : ContentControl, { oldControl.SetTemplatedParent(null); } - + if (change.NewValue is Control newControl) { newControl.SetTemplatedParent(this); @@ -203,7 +203,6 @@ public class AddOnDecoratedInnerBox : ContentControl, } SetupContentPresenterMargin(); - BuildEffectiveInnerBoxPadding(); } private void HandleLayoutSizeChanged(object? sender, SizeChangedEventArgs args) @@ -233,4 +232,10 @@ public class AddOnDecoratedInnerBox : ContentControl, ContentPresenterMargin = new Thickness(marginLeft, 0, marginRight, 0); } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + BuildEffectiveInnerBoxPadding(); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Buttons/DropdownButton.cs b/src/AtomUI.Controls/Buttons/DropdownButton.cs index 1c041da06..5eec4ec37 100644 --- a/src/AtomUI.Controls/Buttons/DropdownButton.cs +++ b/src/AtomUI.Controls/Buttons/DropdownButton.cs @@ -1,7 +1,6 @@ using AtomUI.Data; using AtomUI.Theme; using AtomUI.Theme.Data; -using AtomUI.Theme.Styling; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; @@ -184,12 +183,6 @@ public class DropdownButton : Button ExtraContainerVisible = true; } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - SetupFlyoutProperties(); - } - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); @@ -199,6 +192,7 @@ public class DropdownButton : Button protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); + SetupFlyoutProperties(); _flyoutStateHelper.NotifyAttachedToVisualTree(); } @@ -212,7 +206,7 @@ public class DropdownButton : Button BindUtils.RelayBind(this, IsShowArrowProperty, DropdownFlyout); BindUtils.RelayBind(this, IsPointAtCenterProperty, DropdownFlyout); BindUtils.RelayBind(this, MarginToAnchorProperty, DropdownFlyout); - BindUtils.RelayBind(this, IsMotionEnabledProperty, DropdownFlyout, MenuFlyout.IsMotionEnabledProperty); + BindUtils.RelayBind(this, IsMotionEnabledProperty, DropdownFlyout); DropdownFlyout.Opened += HandleFlyoutOpened; DropdownFlyout.Closed += HandleFlyoutClosed; diff --git a/src/AtomUI.Controls/Buttons/IconButton.cs b/src/AtomUI.Controls/Buttons/IconButton.cs index 495a458d6..5c9e3e42d 100644 --- a/src/AtomUI.Controls/Buttons/IconButton.cs +++ b/src/AtomUI.Controls/Buttons/IconButton.cs @@ -196,9 +196,9 @@ public class IconButton : AvaloniaButton, } } - protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToLogicalTree(e); + base.OnAttachedToVisualTree(e); SetupIcon(); } diff --git a/src/AtomUI.Controls/Buttons/SplitButton.cs b/src/AtomUI.Controls/Buttons/SplitButton.cs index 0313a5a65..be73c6a6c 100644 --- a/src/AtomUI.Controls/Buttons/SplitButton.cs +++ b/src/AtomUI.Controls/Buttons/SplitButton.cs @@ -33,6 +33,7 @@ public class SplitButton : ContentControl, ICommandSource, ISizeTypeAware, IControlSharedTokenResourcesHost, + IAnimationAwareControl, ITokenResourceConsumer { #region 公共属性定义 @@ -98,10 +99,13 @@ public class SplitButton : ContentControl, public static readonly StyledProperty IsPrimaryButtonTypeProperty = AvaloniaProperty.Register(nameof(IsPrimaryButtonType)); + + public static readonly StyledProperty IsMotionEnabledProperty + = AnimationAwareControlProperty.IsMotionEnabledProperty.AddOwner(); - /// - /// Raised when the user presses the primary part of the . - /// + public static readonly StyledProperty IsWaveAnimationEnabledProperty + = AnimationAwareControlProperty.IsWaveAnimationEnabledProperty.AddOwner(); + public event EventHandler? Click { add => AddHandler(ClickEvent, value); @@ -230,6 +234,18 @@ public class SplitButton : ContentControl, get => GetValue(IsPrimaryButtonTypeProperty); set => SetValue(IsPrimaryButtonTypeProperty, value); } + + public bool IsMotionEnabled + { + get => GetValue(IsMotionEnabledProperty); + set => SetValue(IsMotionEnabledProperty, value); + } + + public bool IsWaveAnimationEnabled + { + get => GetValue(IsWaveAnimationEnabledProperty); + set => SetValue(IsWaveAnimationEnabledProperty, value); + } #endregion @@ -248,6 +264,7 @@ public class SplitButton : ContentControl, set => SetAndRaise(EffectiveButtonTypeProperty, ref _effectiveButtonType, value); } + Control IAnimationAwareControl.PropertyBindTarget => this; Control IControlSharedTokenResourcesHost.HostControl => this; string IControlSharedTokenResourcesHost.TokenId => ButtonToken.ID; CompositeDisposable? ITokenResourceConsumer.TokenBindingsDisposable => _tokenBindingsDisposable; @@ -279,14 +296,12 @@ public class SplitButton : ContentControl, { _flyoutStateHelper = new FlyoutStateHelper(); this.RegisterResources(); + this.BindAnimationProperties(IsMotionEnabledProperty, IsWaveAnimationEnabledProperty); } internal virtual bool InternalIsChecked => false; - - /// protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; - - /// + void ICommandSource.CanExecuteChanged(object sender, EventArgs e) { CanExecuteChanged(sender, e); @@ -409,12 +424,21 @@ public class SplitButton : ContentControl, _flyoutStateHelper.NotifyAttachedToVisualTree(); UpdatePseudoClasses(); SetupEffectiveButtonType(); - FlyoutButtonIcon ??= AntDesignIconPackage.EllipsisOutlined(); + + SetupDefaultFlyoutButtonIcon(); SetupFlyoutProperties(); RegisterFlyoutEvents(Flyout); } - /// + private void SetupDefaultFlyoutButtonIcon() + { + if (FlyoutButtonIcon == null) + { + ClearValue(FlyoutButtonIconProperty); + SetValue(FlyoutButtonIconProperty, AntDesignIconPackage.EllipsisOutlined(), BindingPriority.Template); + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -442,16 +466,16 @@ public class SplitButton : ContentControl, BindUtils.RelayBind(this, IsShowArrowProperty, Flyout); BindUtils.RelayBind(this, IsPointAtCenterProperty, Flyout); BindUtils.RelayBind(this, MarginToAnchorProperty, Flyout); + BindUtils.RelayBind(this, MarginToAnchorProperty, Flyout); + BindUtils.RelayBind(this, IsMotionEnabledProperty, Flyout); } } - /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); _tokenBindingsDisposable = new CompositeDisposable(); - // Control attached again, set Hotkey to create a hotkey manager for this control SetCurrentValue(HotKeyProperty, _hotkey); @@ -468,8 +492,7 @@ public class SplitButton : ContentControl, FlyoutStateHelper.MouseLeaveDelayProperty); BindUtils.RelayBind(this, TriggerTypeProperty, _flyoutStateHelper, FlyoutStateHelper.TriggerTypeProperty); } - - /// + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); @@ -484,8 +507,7 @@ public class SplitButton : ContentControl, } this.DisposeTokenBindings(); } - - /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) { if (e.Property == CommandProperty) @@ -578,8 +600,7 @@ public class SplitButton : ContentControl, _secondaryButton.CornerRadius = secondaryButtonCornerRadius; } } - - /// + protected override void OnKeyDown(KeyEventArgs e) { var key = e.Key; @@ -592,8 +613,7 @@ public class SplitButton : ContentControl, base.OnKeyDown(e); } - - /// + protected override void OnKeyUp(KeyEventArgs e) { var key = e.Key; diff --git a/src/AtomUI.Controls/Buttons/ToggleIconButton.cs b/src/AtomUI.Controls/Buttons/ToggleIconButton.cs index 3d3ca0e97..f00cc91a5 100644 --- a/src/AtomUI.Controls/Buttons/ToggleIconButton.cs +++ b/src/AtomUI.Controls/Buttons/ToggleIconButton.cs @@ -6,10 +6,7 @@ using AtomUI.Theme.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; -using Avalonia.Data; -using Avalonia.Layout; using Avalonia.Media; -using Avalonia.VisualTree; namespace AtomUI.Controls; @@ -115,27 +112,8 @@ public class ToggleIconButton : ToggleButton, this.RegisterResources(); } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - if (CheckedIcon is not null) - { - ConfigureIcon(CheckedIcon); - } - - if (UnCheckedIcon is not null) - { - ConfigureIcon(UnCheckedIcon); - } - - ApplyIconToContent(); - } - protected virtual void ConfigureIcon(Icon icon) { - icon.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center, BindingPriority.Template); - icon.SetValue(VerticalAlignmentProperty, VerticalAlignment.Center, BindingPriority.Template); - icon.SetTemplatedParent(this); BindUtils.RelayBind(this, IconWidthProperty, icon, WidthProperty); BindUtils.RelayBind(this, IconHeightProperty, icon, HeightProperty); @@ -151,60 +129,22 @@ public class ToggleIconButton : ToggleButton, protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - if (this.IsAttachedToVisualTree()) - { - if (change.Property == IsCheckedProperty) - { - ApplyIconToContent(); - } - else if (change.Property == IsPressedProperty || - change.Property == IsPointerOverProperty) - { - var icon = IsChecked.HasValue && IsChecked.Value ? CheckedIcon : UnCheckedIcon; - if (icon is not null) - { - if (!PseudoClasses.Contains(StdPseudoClass.Disabled)) - { - icon.IconMode = IconMode.Normal; - if (PseudoClasses.Contains(StdPseudoClass.Pressed)) - { - icon.IconMode = IconMode.Selected; - } - else if (PseudoClasses.Contains(StdPseudoClass.PointerOver)) - { - icon.IconMode = IconMode.Active; - } - } - else - { - icon.IconMode = IconMode.Disabled; - } - } - } - } if (change.Property == CheckedIconProperty || change.Property == UnCheckedIconProperty) { + if (change.OldValue is Icon oldIcon) + { + oldIcon.SetTemplatedParent(null); + } + if (change.NewValue is Icon newIcon) { + newIcon.SetTemplatedParent(this); ConfigureIcon(newIcon); - ApplyIconToContent(); } } } - - internal virtual void ApplyIconToContent() - { - if (IsChecked.HasValue && IsChecked.Value) - { - Content = CheckedIcon; - } - else - { - Content = UnCheckedIcon; - } - } public bool HitTest(Point point) { diff --git a/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs b/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs index 0da958ebf..e5ad81c42 100644 --- a/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs +++ b/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs @@ -4,7 +4,10 @@ using AtomUI.Theme.Styling; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Input; +using Avalonia.Layout; using Avalonia.Styling; namespace AtomUI.Controls; @@ -12,7 +15,9 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class ToggleIconButtonTheme : BaseControlTheme { - public const string ContentPresenterPart = "PART_ContentPresenter"; + public const string RootLayoutPart = "PART_RootLayout"; + public const string CheckedIconPresenterPart = "PART_CheckedIconPresenter"; + public const string UnCheckedIconPresenterPart = "PART_UnCheckedIconPresenter"; public ToggleIconButtonTheme() : base(typeof(ToggleIconButton)) @@ -27,23 +32,52 @@ internal class ToggleIconButtonTheme : BaseControlTheme { return new FuncControlTemplate((button, scope) => { - var contentPresenter = new ContentPresenter + var rootLayout = new Panel() { - Name = ContentPresenterPart + Name = RootLayoutPart }; - CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, - ContentControl.ContentProperty); - CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, - ContentControl.ContentTemplateProperty); - return contentPresenter; + var checkedIconPresenter = new ContentPresenter + { + Name = CheckedIconPresenterPart + }; + + var uncheckedIconPresenter = new ContentPresenter() + { + Name = UnCheckedIconPresenterPart + }; + + rootLayout.Children.Add(checkedIconPresenter); + rootLayout.Children.Add(uncheckedIconPresenter); + + CreateTemplateParentBinding(checkedIconPresenter, ContentPresenter.ContentProperty, + ToggleIconButton.CheckedIconProperty); + CreateTemplateParentBinding(checkedIconPresenter, ContentPresenter.IsVisibleProperty, + ToggleIconButton.IsCheckedProperty, BindingMode.Default, + new FuncValueConverter(input => input.HasValue && input.Value)); + + CreateTemplateParentBinding(uncheckedIconPresenter, ContentPresenter.ContentProperty, + ToggleIconButton.UnCheckedIconProperty); + CreateTemplateParentBinding(uncheckedIconPresenter, ContentPresenter.IsVisibleProperty, + ToggleIconButton.IsCheckedProperty, BindingMode.Default, + new FuncValueConverter(input => !input.HasValue || !input.Value)); + + return rootLayout; }); } protected override void BuildStyles() { + { + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(Icon.HorizontalAlignmentProperty, HorizontalAlignment.Center); + iconStyle.Add(Icon.VerticalAlignmentProperty, VerticalAlignment.Center); + Add(iconStyle); + } + var enabledStyle = new Style(selector => selector.Nesting().PropertyEquals(InputElement.IsEnabledProperty, true)); - enabledStyle.Add(ToggleIconButton.CursorProperty, new SetterValueFactory(() => new Cursor(StandardCursorType.Hand))); + enabledStyle.Add(ToggleIconButton.CursorProperty, + new SetterValueFactory(() => new Cursor(StandardCursorType.Hand))); { { var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); diff --git a/src/AtomUI.Controls/TreeView/TreeViewItem.cs b/src/AtomUI.Controls/TreeView/TreeViewItem.cs index 7ad939290..80d61d55e 100644 --- a/src/AtomUI.Controls/TreeView/TreeViewItem.cs +++ b/src/AtomUI.Controls/TreeView/TreeViewItem.cs @@ -527,10 +527,12 @@ public class TreeViewItem : AvaloniaTreeItem, var motion = new ExpandMotion(Direction.Top, MotionDuration, new CubicEaseOut()); - MotionInvoker.Invoke(_itemsPresenterMotionActor, motion, null, - () => + MotionInvoker.Invoke(_itemsPresenterMotionActor, motion, () => { _itemsPresenterMotionActor.IsVisible = true; + }, + () => + { _animating = false; if (_switcherButton != null) {