From 4aeae4040c37d2dd88ceb00c8c19df5e6d1d5c93 Mon Sep 17 00:00:00 2001 From: chinboy Date: Fri, 24 Jan 2025 11:14:12 +0800 Subject: [PATCH] Refactor NodeSwitcherButton --- .../Buttons/ToggleIconButtonTheme.cs | 2 +- .../TreeView/NodeSwitcherButton.cs | 211 ++++++++++------- .../TreeView/NodeSwitcherButtonTheme.cs | 167 ++++++++++++- src/AtomUI.Controls/TreeView/TreeView.cs | 14 +- src/AtomUI.Controls/TreeView/TreeViewItem.cs | 224 +++++++++++++----- .../TreeView/TreeViewItemTheme.cs | 24 +- 6 files changed, 476 insertions(+), 166 deletions(-) diff --git a/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs b/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs index 819b55a51..bbe5e79d1 100644 --- a/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs +++ b/src/AtomUI.Controls/Buttons/ToggleIconButtonTheme.cs @@ -50,7 +50,7 @@ internal class ToggleIconButtonTheme : BaseControlTheme enabledStyle.Add(iconStyle); } - var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); + var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); { var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); iconStyle.Add(Icon.IconModeProperty, IconMode.Active); diff --git a/src/AtomUI.Controls/TreeView/NodeSwitcherButton.cs b/src/AtomUI.Controls/TreeView/NodeSwitcherButton.cs index 0c40aa7fb..89391ee18 100644 --- a/src/AtomUI.Controls/TreeView/NodeSwitcherButton.cs +++ b/src/AtomUI.Controls/TreeView/NodeSwitcherButton.cs @@ -1,25 +1,34 @@ -using AtomUI.Controls.Utils; -using AtomUI.Data; +using AtomUI.Controls.Utils; using AtomUI.IconPkg; using AtomUI.IconPkg.AntDesign; using AtomUI.Media; -using AtomUI.Theme.Data; -using AtomUI.Theme.Styling; using Avalonia; using Avalonia.Animation; using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; namespace AtomUI.Controls; -/// -/// 如果 checked icon 和 unchecked icon 都设置的时候,就直接切换 -/// 否则就直接改变 render transform -/// -internal class NodeSwitcherButton : ToggleIconButton +internal enum NodeSwitcherButtonIconMode +{ + Default, + Rotation, + Leaf, + Loading +} + +internal class NodeSwitcherButton : ToggleButton { #region 公共属性 + + public static readonly StyledProperty ExpandIconProperty + = AvaloniaProperty.Register(nameof(ExpandIcon)); + + public static readonly StyledProperty CollapseIconProperty + = AvaloniaProperty.Register(nameof(CollapseIcon)); public static readonly StyledProperty LoadingIconProperty = AvaloniaProperty.Register(nameof(LoadingIcon)); @@ -29,6 +38,12 @@ internal class NodeSwitcherButton : ToggleIconButton public static readonly StyledProperty IsLeafProperty = AvaloniaProperty.Register(nameof(IsLeaf)); + + public static readonly StyledProperty IsLoadingProperty + = AvaloniaProperty.Register(nameof(IsLoading), false); + + public static readonly StyledProperty RotationIconProperty + = AvaloniaProperty.Register(nameof(RotationIcon)); public Icon? LoadingIcon { @@ -47,18 +62,82 @@ internal class NodeSwitcherButton : ToggleIconButton get => GetValue(IsLeafProperty); set => SetValue(IsLeafProperty, value); } + + public bool IsLoading + { + get => GetValue(IsLoadingProperty); + set => SetValue(IsLoadingProperty, value); + } + + public Icon? RotationIcon + { + get => GetValue(RotationIconProperty); + set => SetValue(RotationIconProperty, value); + } + + public Icon? ExpandIcon + { + get => GetValue(ExpandIconProperty); + set => SetValue(ExpandIconProperty, value); + } + public Icon? CollapseIcon + { + get => GetValue(CollapseIconProperty); + set => SetValue(CollapseIconProperty, value); + } + #endregion #region 内部属性定义 - internal static readonly StyledProperty IsIconVisibleProperty - = AvaloniaProperty.Register(nameof(IsIconVisible), true); + internal static readonly StyledProperty IsLeafIconVisibleProperty + = AvaloniaProperty.Register(nameof(IsLeafIconVisible), true); + + internal static readonly DirectProperty IconModeProperty = + AvaloniaProperty.RegisterDirect( + nameof(IconMode), + o => o.IconMode, + (o, v) => o.IconMode = v); + + internal static readonly DirectProperty ExpandIconVisibleProperty = + AvaloniaProperty.RegisterDirect( + nameof(ExpandIconVisible), + o => o.ExpandIconVisible, + (o, v) => o.ExpandIconVisible = v); + + internal static readonly DirectProperty CollapseIconVisibleProperty = + AvaloniaProperty.RegisterDirect( + nameof(CollapseIconVisible), + o => o.CollapseIconVisible, + (o, v) => o.CollapseIconVisible = v); - internal bool IsIconVisible + internal bool IsLeafIconVisible { - get => GetValue(IsIconVisibleProperty); - set => SetValue(IsIconVisibleProperty, value); + get => GetValue(IsLeafIconVisibleProperty); + set => SetValue(IsLeafIconVisibleProperty, value); + } + + private NodeSwitcherButtonIconMode _iconMode; + + internal NodeSwitcherButtonIconMode IconMode + { + get => _iconMode; + set => SetAndRaise(IconModeProperty, ref _iconMode, value); + } + + private bool _expandIconVisible; + internal bool ExpandIconVisible + { + get => _expandIconVisible; + set => SetAndRaise(ExpandIconVisibleProperty, ref _expandIconVisible, value); + } + + private bool _collapseIconVisible; + internal bool CollapseIconVisible + { + get => _collapseIconVisible; + set => SetAndRaise(CollapseIconVisibleProperty, ref _collapseIconVisible, value); } #endregion @@ -67,119 +146,73 @@ internal class NodeSwitcherButton : ToggleIconButton static NodeSwitcherButton() { - AffectsMeasure(LoadingIconProperty, LeafIconProperty); AffectsRender(BackgroundProperty); + AffectsMeasure(ExpandIconProperty, CollapseIconProperty, IsCheckedProperty, + LoadingIconProperty, LeafIconProperty); } public NodeSwitcherButton() { + SetCurrentValue(CursorProperty, new Cursor(StandardCursorType.Hand)); _borderRenderHelper = new BorderRenderHelper(); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - LoadingIcon ??= AntDesignIconPackage.LoadingOutlined(); - ConfigureFixedSizeIcon(LoadingIcon); - LoadingIcon.LoadingAnimation = IconAnimation.Spin; - - LeafIcon ??= AntDesignIconPackage.FileOutlined(); - - ConfigureFixedSizeIcon(LeafIcon); + SetupIconVisibility(IsChecked ?? false); base.OnApplyTemplate(e); - ApplyIconToContent(); Transitions ??= new Transitions { AnimationUtils.CreateTransition(BackgroundProperty) }; } - private void ConfigureFixedSizeIcon(Icon icon) - { - icon.SetCurrentValue(HorizontalAlignmentProperty, HorizontalAlignment.Center); - icon.SetCurrentValue(VerticalAlignmentProperty, VerticalAlignment.Center); - UIStructureUtils.SetTemplateParent(icon, this); - TokenResourceBinder.CreateGlobalResourceBinding(icon, WidthProperty, GlobalTokenResourceKey.IconSize); - TokenResourceBinder.CreateGlobalResourceBinding(icon, HeightProperty, GlobalTokenResourceKey.IconSize); - BindUtils.RelayBind(this, IsIconVisibleProperty, icon, IsVisibleProperty); - } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - if (VisualRoot is not null) + if (change.Property == CollapseIconProperty || + change.Property == ExpandIconProperty || + change.Property == LoadingIconProperty || + change.Property == RotationIconProperty) { - if (change.Property == IsLeafProperty) + if (change.NewValue is Icon icon) { - ApplyIconToContent(); + icon.HorizontalAlignment = HorizontalAlignment.Center; + icon.VerticalAlignment = VerticalAlignment.Center; } } - - if (change.Property == LoadingIconProperty || - change.Property == LeafIconProperty) + else if (change.Property == IsCheckedProperty) { - if (change.NewValue is Icon newIcon) - { - ConfigureFixedSizeIcon(newIcon); - ApplyIconToContent(); - RenderTransform = null; - } - } - - if (change.Property == CheckedIconProperty || - change.Property == UnCheckedIconProperty) - { - RenderTransform = null; - ApplyIconToContent(); + var isChecked = (bool?)change.NewValue ?? false; + SetupIconVisibility(isChecked); } } - internal override void ApplyIconToContent() + private void SetupIconVisibility(bool isChecked) { - if (!IsLeaf) + if (_iconMode != NodeSwitcherButtonIconMode.Default) { - if (IsChecked.HasValue) - { - if (CheckedIcon is not null && UnCheckedIcon is not null) - { - // 直接切换模式 - if (IsChecked.Value) - { - Content = CheckedIcon; - } - else - { - Content = UnCheckedIcon; - } - } - else if (UnCheckedIcon is not null) - { - // 通过 render transform 进行设置 - Content = UnCheckedIcon; - if (IsChecked.Value) - { - RenderTransform = new RotateTransform(90); - } - else - { - RenderTransform = null; - } - } - } - else - { - Content = LoadingIcon; - } + ExpandIconVisible = false; + CollapseIconVisible = false; } else { - RenderTransform = null; - Content = LeafIcon; + if (isChecked) + { + ExpandIconVisible = false; + CollapseIconVisible = true; + } + else + { + ExpandIconVisible = true; + CollapseIconVisible = false; + } } } public override void Render(DrawingContext context) { - if (IsIconVisible && !IsLeaf) + if (_iconMode != NodeSwitcherButtonIconMode.Leaf) { _borderRenderHelper.Render(context, Bounds.Size, diff --git a/src/AtomUI.Controls/TreeView/NodeSwitcherButtonTheme.cs b/src/AtomUI.Controls/TreeView/NodeSwitcherButtonTheme.cs index a9ffa848b..8d86a23d1 100644 --- a/src/AtomUI.Controls/TreeView/NodeSwitcherButtonTheme.cs +++ b/src/AtomUI.Controls/TreeView/NodeSwitcherButtonTheme.cs @@ -1,28 +1,181 @@ -using AtomUI.Theme.Styling; +using AtomUI.IconPkg; +using AtomUI.IconPkg.AntDesign; +using AtomUI.Theme; +using AtomUI.Theme.Styling; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Input; +using Avalonia.Layout; +using Avalonia.Media; using Avalonia.Styling; namespace AtomUI.Controls; [ControlThemeProvider] -internal class NodeSwitcherButtonTheme : ToggleIconButtonTheme +internal class NodeSwitcherButtonTheme : BaseControlTheme { + public const string RootPart = "PART_Root"; + public const string RotationIconPart = "PART_RotationIcon"; + public const string ExpandIconPart = "PART_ExpandIcon"; + public const string CollapseIconPart = "PART_CollapseIcon"; + public const string LoadingIconPart = "PART_LoadingIcon"; + public const string LeafIconPart = "PART_LeafIcon"; + public NodeSwitcherButtonTheme() : base(typeof(NodeSwitcherButton)) { } + + protected override IControlTemplate BuildControlTemplate() + { + return new FuncControlTemplate((button, scope) => + { + var rootContainer = new Panel + { + Name = RootPart, + IsHitTestVisible = false + }; + + rootContainer.RegisterInNameScope(scope); + + // 我们需要在这个切换按钮显示几种状态的图标 + // 1、展开和收缩两种状态的图标 + // 2、只设置一个图标,展开和收缩通过旋转开表示 + // 3、当节点是叶子节点的时候,没有展开和收缩的概念,直接显示叶子节点的图标 + // 4、加载状态的图标 + var expandIconPresenter = new ContentPresenter + { + Name = ExpandIconPart, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + }; + expandIconPresenter.RegisterInNameScope(scope); + + CreateTemplateParentBinding(expandIconPresenter, ContentPresenter.ContentProperty, + NodeSwitcherButton.ExpandIconProperty); + CreateTemplateParentBinding(expandIconPresenter, ContentPresenter.IsVisibleProperty, + NodeSwitcherButton.ExpandIconVisibleProperty); + + var collapseIconPresenter = new ContentPresenter + { + Name = CollapseIconPart, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + }; + collapseIconPresenter.RegisterInNameScope(scope); + CreateTemplateParentBinding(collapseIconPresenter, ContentPresenter.ContentProperty, + NodeSwitcherButton.CollapseIconProperty); + CreateTemplateParentBinding(collapseIconPresenter, ContentPresenter.IsVisibleProperty, + NodeSwitcherButton.CollapseIconVisibleProperty); + + var rotationIconPresenter = new ContentPresenter + { + Name = RotationIconPart, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + }; + rotationIconPresenter.RegisterInNameScope(scope); + CreateTemplateParentBinding(rotationIconPresenter, ContentPresenter.ContentProperty, + NodeSwitcherButton.RotationIconProperty); + CreateTemplateParentBinding(rotationIconPresenter, ContentPresenter.IsVisibleProperty, + NodeSwitcherButton.IconModeProperty, BindingMode.Default, new FuncValueConverter(iconMode => + { + return iconMode == NodeSwitcherButtonIconMode.Rotation; + })); + + var loadingIconPresenter = new ContentPresenter + { + Name = LoadingIconPart, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + }; + loadingIconPresenter.RegisterInNameScope(scope); + + CreateTemplateParentBinding(loadingIconPresenter, ContentPresenter.ContentProperty, + NodeSwitcherButton.LoadingIconProperty); + CreateTemplateParentBinding(loadingIconPresenter, ContentPresenter.IsVisibleProperty, + NodeSwitcherButton.IconModeProperty, BindingMode.Default, new FuncValueConverter(iconMode => + { + return iconMode == NodeSwitcherButtonIconMode.Loading; + })); + + var leafIconPresenter = new ContentPresenter + { + Name = LeafIconPart, + HorizontalContentAlignment = HorizontalAlignment.Center, + VerticalContentAlignment = VerticalAlignment.Center, + }; + leafIconPresenter.RegisterInNameScope(scope); + + CreateTemplateParentBinding(leafIconPresenter, ContentPresenter.ContentProperty, + NodeSwitcherButton.LeafIconProperty); + CreateTemplateParentBinding(leafIconPresenter, ContentPresenter.IsVisibleProperty, + NodeSwitcherButton.IsLeafIconVisibleProperty); + + rootContainer.Children.Add(expandIconPresenter); + rootContainer.Children.Add(collapseIconPresenter); + rootContainer.Children.Add(rotationIconPresenter); + rootContainer.Children.Add(loadingIconPresenter); + rootContainer.Children.Add(leafIconPresenter); + + return rootContainer; + }); + } protected override void BuildStyles() { base.BuildStyles(); - + var commonStyle = new Style(selector => selector.Nesting()); commonStyle.Add(TemplatedControl.BackgroundProperty, GlobalTokenResourceKey.ColorTransparent); commonStyle.Add(TemplatedControl.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadius); - - var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); - hoverStyle.Add(TemplatedControl.BackgroundProperty, TreeViewTokenResourceKey.NodeHoverBg); - commonStyle.Add(hoverStyle); + + var checkStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Checked) + .PropertyEquals(NodeSwitcherButton.IconModeProperty, NodeSwitcherButtonIconMode.Rotation)); + checkStyle.Add(NodeSwitcherButton.RenderTransformProperty, new RotateTransform(90)); + commonStyle.Add(checkStyle); + Add(commonStyle); + + var enabledStyle = + new Style(selector => selector.Nesting().PropertyEquals(InputElement.IsEnabledProperty, true)); + { + { + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(Icon.IconModeProperty, IconMode.Normal); + enabledStyle.Add(iconStyle); + } + + var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); + { + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(Icon.IconModeProperty, IconMode.Active); + hoverStyle.Add(iconStyle); + } + hoverStyle.Add(TemplatedControl.BackgroundProperty, TreeViewTokenResourceKey.NodeHoverBg); + enabledStyle.Add(hoverStyle); + + var checkedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); + { + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(Icon.IconModeProperty, IconMode.Selected); + checkedStyle.Add(iconStyle); + } + enabledStyle.Add(checkedStyle); + } + Add(enabledStyle); + + var disabledStyle = + new Style(selector => selector.Nesting().PropertyEquals(InputElement.IsEnabledProperty, false)); + { + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(Icon.IconModeProperty, IconMode.Disabled); + disabledStyle.Add(iconStyle); + } + Add(disabledStyle); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TreeView/TreeView.cs b/src/AtomUI.Controls/TreeView/TreeView.cs index 75d27fa3d..8bb78d790 100644 --- a/src/AtomUI.Controls/TreeView/TreeView.cs +++ b/src/AtomUI.Controls/TreeView/TreeView.cs @@ -46,8 +46,8 @@ public class TreeView : AvaloniaTreeView public static readonly StyledProperty NodeHoverModeProperty = AvaloniaProperty.Register(nameof(NodeHoverMode)); - public static readonly StyledProperty IsShowLeafSwitcherProperty = - AvaloniaProperty.Register(nameof(IsShowLeafSwitcher)); + public static readonly StyledProperty IsShowLeafIconProperty = + AvaloniaProperty.Register(nameof(IsShowLeafIcon)); public bool IsDraggable { @@ -79,10 +79,10 @@ public class TreeView : AvaloniaTreeView set => SetValue(NodeHoverModeProperty, value); } - public bool IsShowLeafSwitcher + public bool IsShowLeafIcon { - get => GetValue(IsShowLeafSwitcherProperty); - set => SetValue(IsShowLeafSwitcherProperty, value); + get => GetValue(IsShowLeafIconProperty); + set => SetValue(IsShowLeafIconProperty, value); } public bool IsDefaultExpandAll { get; set; } = false; @@ -274,8 +274,8 @@ public class TreeView : AvaloniaTreeView BindUtils.RelayBind(this, NodeHoverModeProperty, treeViewItem, TreeViewItem.NodeHoverModeProperty); BindUtils.RelayBind(this, IsShowLineProperty, treeViewItem, TreeViewItem.IsShowLineProperty); BindUtils.RelayBind(this, IsShowIconProperty, treeViewItem, TreeViewItem.IsShowIconProperty); - BindUtils.RelayBind(this, IsShowLeafSwitcherProperty, treeViewItem, - TreeViewItem.IsShowLeafSwitcherProperty); + BindUtils.RelayBind(this, IsShowLeafIconProperty, treeViewItem, + TreeViewItem.IsShowLeafIconProperty); BindUtils.RelayBind(this, IsCheckableProperty, treeViewItem, TreeViewItem.IsCheckboxVisibleProperty); } } diff --git a/src/AtomUI.Controls/TreeView/TreeViewItem.cs b/src/AtomUI.Controls/TreeView/TreeViewItem.cs index e05234757..acb0881a5 100644 --- a/src/AtomUI.Controls/TreeView/TreeViewItem.cs +++ b/src/AtomUI.Controls/TreeView/TreeViewItem.cs @@ -39,11 +39,26 @@ public class TreeViewItem : AvaloniaTreeItem public static readonly StyledProperty SwitcherCollapseIconProperty = AvaloniaProperty.Register(nameof(SwitcherCollapseIcon)); - + + public static readonly StyledProperty SwitcherRotationIconProperty + = AvaloniaProperty.Register(nameof(SwitcherRotationIcon)); + + public static readonly StyledProperty SwitcherLoadingIconProperty + = AvaloniaProperty.Register(nameof(SwitcherRotationIcon)); + + public static readonly StyledProperty SwitcherLeafIconProperty + = AvaloniaProperty.Register(nameof(SwitcherLeafIcon)); + public static readonly DirectProperty IsLeafProperty = AvaloniaProperty.RegisterDirect(nameof(IsLeaf), o => o.IsLeaf, (o, v) => o.IsLeaf = v); + + public static readonly StyledProperty IsShowLeafIconProperty = + AvaloniaProperty.Register(nameof(IsShowLeafIcon)); + + public static readonly StyledProperty IsLoadingProperty + = AvaloniaProperty.Register(nameof(IsLoading), false); public bool IsCheckable { @@ -69,6 +84,30 @@ public class TreeViewItem : AvaloniaTreeItem set => SetValue(SwitcherCollapseIconProperty, value); } + public Icon? SwitcherRotationIcon + { + get => GetValue(SwitcherRotationIconProperty); + set => SetValue(SwitcherRotationIconProperty, value); + } + + public Icon? SwitcherLoadingIcon + { + get => GetValue(SwitcherLoadingIconProperty); + set => SetValue(SwitcherLoadingIconProperty, value); + } + + public Icon? SwitcherLeafIcon + { + get => GetValue(SwitcherLeafIconProperty); + set => SetValue(SwitcherLeafIconProperty, value); + } + + internal bool IsShowLeafIcon + { + get => GetValue(IsShowLeafIconProperty); + set => SetValue(IsShowLeafIconProperty, value); + } + public bool? IsChecked { get => GetValue(IsCheckedProperty); @@ -82,6 +121,12 @@ public class TreeViewItem : AvaloniaTreeItem get => _isLeaf; internal set => SetAndRaise(IsLeafProperty, ref _isLeaf, value); } + + public bool IsLoading + { + get => GetValue(IsLoadingProperty); + set => SetValue(IsLoadingProperty, value); + } public TreeNodeKey? Key { get; set; } @@ -114,11 +159,6 @@ public class TreeViewItem : AvaloniaTreeItem o => o.IconEffectiveVisible, (o, v) => o.IconEffectiveVisible = v); - internal static readonly DirectProperty IsShowLeafSwitcherProperty = - AvaloniaProperty.RegisterDirect(nameof(IsShowLeafSwitcher), - o => o.IsShowLeafSwitcher, - (o, v) => o.IsShowLeafSwitcher = v); - internal static readonly DirectProperty IsCheckboxVisibleProperty = AvaloniaProperty.RegisterDirect(nameof(IsCheckboxVisible), o => o.IsCheckboxVisible, @@ -190,14 +230,6 @@ public class TreeViewItem : AvaloniaTreeItem set => SetAndRaise(IconEffectiveVisibleProperty, ref _iconEffectiveVisible, value); } - private bool _isShowLeafSwitcher; - - internal bool IsShowLeafSwitcher - { - get => _isShowLeafSwitcher; - set => SetAndRaise(IsShowLeafSwitcherProperty, ref _isShowLeafSwitcher, value); - } - private bool _isCheckboxVisible; internal bool IsCheckboxVisible @@ -267,7 +299,7 @@ public class TreeViewItem : AvaloniaTreeItem AffectsRender(EffectiveNodeCornerRadiusProperty, EffectiveNodeBgProperty, IsShowLineProperty, - IsShowLeafSwitcherProperty, + IsShowLeafIconProperty, IsDraggingProperty, IsDragOverProperty); } @@ -289,7 +321,10 @@ public class TreeViewItem : AvaloniaTreeItem // 注册到 TreeView OwnerTreeView?.DefaultCheckedItems.Add(this); } - + + CreateNodeSwitcherDefaultIcons(); + SetNodeSwitcherIcons(); + SetupSwitcherButtonIconMode(); _initialized = true; } } @@ -303,11 +338,12 @@ public class TreeViewItem : AvaloniaTreeItem { CalculateEffectiveBgRect(); } - else if (change.Property == IsShowLineProperty || - change.Property == SwitcherExpandIconProperty || - change.Property == SwitcherCollapseIconProperty) + else if (change.Property == SwitcherExpandIconProperty || + change.Property == SwitcherCollapseIconProperty || + change.Property == SwitcherRotationIconProperty) { SetNodeSwitcherIcons(); + SetupSwitcherButtonIconMode(); } else if (change.Property == IsEnabledProperty || change.Property == IsCheckableProperty) @@ -328,6 +364,11 @@ public class TreeViewItem : AvaloniaTreeItem OwnerTreeView?.UnCheckedSubTree(this); } } + } + else if (change.Property == IsShowLineProperty) + { + CreateNodeSwitcherDefaultIcons(); + SetNodeSwitcherIcons(); } } @@ -351,6 +392,74 @@ public class TreeViewItem : AvaloniaTreeItem { UIStructureUtils.SetTemplateParent(newIcon, this); } + } + else if (change.Property == IsLoadingProperty || change.Property == IsLeafProperty) + { + SetupSwitcherButtonIconMode(); + } + } + + private void SetupSwitcherButtonIconMode() + { + if (_switcherButton is not null) + { + if (!IsLeaf) + { + if (IsLoading) + { + _switcherButton.IconMode = NodeSwitcherButtonIconMode.Loading; + } + else + { + if (SwitcherExpandIcon != null && SwitcherCollapseIcon != null) + { + _switcherButton.IconMode = NodeSwitcherButtonIconMode.Default; + } + else if (SwitcherRotationIcon != null) + { + _switcherButton.IconMode = NodeSwitcherButtonIconMode.Rotation; + } + } + } + else + { + _switcherButton.IconMode = NodeSwitcherButtonIconMode.Leaf; + } + } + } + + private void CreateNodeSwitcherDefaultIcons() + { + if (IsShowLine) + { + if (SwitcherExpandIcon == null) + { + SetValue(SwitcherExpandIconProperty, AntDesignIconPackage.PlusSquareOutlined(), BindingPriority.Template); + } + + if (SwitcherCollapseIcon == null) + { + SetValue(SwitcherCollapseIconProperty, AntDesignIconPackage.MinusSquareOutlined(), BindingPriority.Template); + } + } + else + { + if (SwitcherRotationIcon == null) + { + SetValue(SwitcherRotationIconProperty, AntDesignIconPackage.CaretRightOutlined(), BindingPriority.Template); + } + } + + if (SwitcherLoadingIcon == null) + { + var loadingIcon = AntDesignIconPackage.LoadingOutlined(); + loadingIcon.LoadingAnimation = IconAnimation.Spin; + SetValue(SwitcherLoadingIconProperty, loadingIcon, BindingPriority.Template); + } + + if (SwitcherLeafIcon == null) + { + SetValue(SwitcherLeafIconProperty, AntDesignIconPackage.FileOutlined(), BindingPriority.Template); } } @@ -367,12 +476,15 @@ public class TreeViewItem : AvaloniaTreeItem GlobalTokenResourceKey.BorderThickness, BindingPriority.Template, new RenderScaleAwareThicknessConfigure(this)); - + base.OnApplyTemplate(e); + SetNodeSwitcherIcons(); _headerPresenter = e.NameScope.Find(TreeViewItemTheme.HeaderPresenterPart); _iconPresenter = e.NameScope.Find(TreeViewItemTheme.IconPresenterPart); _frameDecorator = e.NameScope.Find(TreeViewItemTheme.FrameDecoratorPart); _switcherButton = e.NameScope.Find(TreeViewItemTheme.NodeSwitcherButtonPart); + + SetupSwitcherButtonIconMode(); if (_frameDecorator is not null) { @@ -393,7 +505,6 @@ public class TreeViewItem : AvaloniaTreeItem } IsLeaf = ItemCount == 0; - SetNodeSwitcherIcons(); SetupCheckBoxEnabled(); Transitions ??= new Transitions @@ -463,8 +574,17 @@ public class TreeViewItem : AvaloniaTreeItem { EdgeMode = EdgeMode.Aliased }); + + var isLastChild = false; + if (Parent is ItemsControl parentTreeItem) + { + if (parentTreeItem.ContainerFromIndex(parentTreeItem.ItemCount - 1) == this) + { + isLastChild = true; + } + } - if (!IsLeaf) + if (!IsLeaf && !isLastChild) { var switcherMiddleBottom = _switcherButton.TranslatePoint( @@ -477,17 +597,8 @@ public class TreeViewItem : AvaloniaTreeItem } // 画孩子线条 - if (!IsShowLeafSwitcher && IsLeaf) + if (!IsShowLeafIcon && IsLeaf) { - var isLastChild = false; - if (Parent is TreeViewItem parentTreeItem) - { - if (parentTreeItem.ContainerFromIndex(parentTreeItem.ItemCount - 1) == this) - { - isLastChild = true; - } - } - { // 纵向 var childStartPoint = @@ -525,39 +636,44 @@ public class TreeViewItem : AvaloniaTreeItem private void SetNodeSwitcherIcons() { - if (_switcherButton is null) + if (SwitcherExpandIcon is not null) { - return; + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherExpandIcon, WidthProperty, + GlobalTokenResourceKey.IconSize); + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherExpandIcon, HeightProperty, + GlobalTokenResourceKey.IconSize); } - if (SwitcherExpandIcon is not null || SwitcherCollapseIcon is not null) + if (SwitcherCollapseIcon is not null) { - _switcherButton.UnCheckedIcon = SwitcherExpandIcon; - _switcherButton.CheckedIcon = SwitcherCollapseIcon; - TokenResourceBinder.CreateTokenBinding(_switcherButton, ToggleIconButton.IconWidthProperty, + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherCollapseIcon, WidthProperty, GlobalTokenResourceKey.IconSize); - TokenResourceBinder.CreateTokenBinding(_switcherButton, ToggleIconButton.IconHeightProperty, + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherCollapseIcon, HeightProperty, GlobalTokenResourceKey.IconSize); - return; } - if (IsShowLine) + if (SwitcherRotationIcon is not null) { - _switcherButton.UnCheckedIcon = AntDesignIconPackage.PlusSquareOutlined(); - _switcherButton.CheckedIcon = AntDesignIconPackage.MinusSquareOutlined(); - TokenResourceBinder.CreateTokenBinding(_switcherButton, ToggleIconButton.IconWidthProperty, + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherRotationIcon, WidthProperty, + GlobalTokenResourceKey.IconSizeXS); + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherRotationIcon, HeightProperty, + GlobalTokenResourceKey.IconSizeXS); + } + + if (SwitcherLoadingIcon is not null) + { + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherLoadingIcon, WidthProperty, GlobalTokenResourceKey.IconSize); - TokenResourceBinder.CreateTokenBinding(_switcherButton, ToggleIconButton.IconHeightProperty, + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherLoadingIcon, HeightProperty, GlobalTokenResourceKey.IconSize); } - else + + if (SwitcherLeafIcon is not null) { - _switcherButton.CheckedIcon = null; - _switcherButton.UnCheckedIcon = AntDesignIconPackage.CaretRightOutlined(); - TokenResourceBinder.CreateTokenBinding(_switcherButton, ToggleIconButton.IconWidthProperty, - GlobalTokenResourceKey.IconSizeXS); - TokenResourceBinder.CreateTokenBinding(_switcherButton, ToggleIconButton.IconHeightProperty, - GlobalTokenResourceKey.IconSizeXS); + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherLeafIcon, WidthProperty, + GlobalTokenResourceKey.IconSize); + TokenResourceBinder.CreateGlobalResourceBinding(SwitcherLeafIcon, HeightProperty, + GlobalTokenResourceKey.IconSize); } } @@ -642,7 +758,7 @@ public class TreeViewItem : AvaloniaTreeItem } } - if (_switcherButton is not null && _switcherButton.IsIconVisible) + if (_switcherButton is not null && _switcherButton.IsLeafIconVisible) { offsetX -= _switcherButton.Bounds.Width; } @@ -681,6 +797,4 @@ public class TreeViewItem : AvaloniaTreeItem { return new DragPreviewAdorner(_frameDecorator!); } - - // #endregion } \ No newline at end of file diff --git a/src/AtomUI.Controls/TreeView/TreeViewItemTheme.cs b/src/AtomUI.Controls/TreeView/TreeViewItemTheme.cs index 07de37858..cf06b9253 100644 --- a/src/AtomUI.Controls/TreeView/TreeViewItemTheme.cs +++ b/src/AtomUI.Controls/TreeView/TreeViewItemTheme.cs @@ -74,14 +74,22 @@ internal class TreeViewItemTheme : BaseControlTheme Focusable = false }; nodeSwitcherButton.RegisterInNameScope(scope); - CreateTemplateParentBinding(nodeSwitcherButton, ToggleIconButton.CheckedIconProperty, + CreateTemplateParentBinding(nodeSwitcherButton, NodeSwitcherButton.CollapseIconProperty, TreeViewItem.SwitcherCollapseIconProperty); - CreateTemplateParentBinding(nodeSwitcherButton, ToggleIconButton.UnCheckedIconProperty, + CreateTemplateParentBinding(nodeSwitcherButton, NodeSwitcherButton.ExpandIconProperty, TreeViewItem.SwitcherExpandIconProperty); + CreateTemplateParentBinding(nodeSwitcherButton, NodeSwitcherButton.RotationIconProperty, + TreeViewItem.SwitcherRotationIconProperty); + CreateTemplateParentBinding(nodeSwitcherButton, NodeSwitcherButton.LoadingIconProperty, + TreeViewItem.SwitcherLoadingIconProperty); + CreateTemplateParentBinding(nodeSwitcherButton, NodeSwitcherButton.LeafIconProperty, + TreeViewItem.SwitcherLeafIconProperty); CreateTemplateParentBinding(nodeSwitcherButton, ToggleButton.IsCheckedProperty, Avalonia.Controls.TreeViewItem.IsExpandedProperty, BindingMode.TwoWay); CreateTemplateParentBinding(nodeSwitcherButton, NodeSwitcherButton.IsLeafProperty, TreeViewItem.IsLeafProperty); + CreateTemplateParentBinding(nodeSwitcherButton, NodeSwitcherButton.IsLoadingProperty, + TreeViewItem.IsLoadingProperty); treeItemLayout.Children.Add(nodeSwitcherButton); Grid.SetColumn(nodeSwitcherButton, 0); @@ -241,13 +249,15 @@ internal class TreeViewItemTheme : BaseControlTheme } Add(leafSwitcherButtonStyle); - var leafAndHideButtonStyle = new Style(selector => selector - .Nesting().PropertyEquals(TreeViewItem.IsLeafProperty, true) - .PropertyEquals(TreeViewItem.IsShowLeafSwitcherProperty, - false)); + var leafAndHideButtonStyle = new Style(selector => Selectors.Or(selector + .Nesting().PropertyEquals(TreeViewItem.IsLeafProperty, true) + .PropertyEquals(TreeViewItem.IsShowLeafIconProperty, + false), + selector + .Nesting().PropertyEquals(TreeViewItem.IsLeafProperty, false))); { var switcherButtonStyle = new Style(selector => selector.Nesting().Template().Name(NodeSwitcherButtonPart)); - switcherButtonStyle.Add(NodeSwitcherButton.IsIconVisibleProperty, false); + switcherButtonStyle.Add(NodeSwitcherButton.IsLeafIconVisibleProperty, false); leafAndHideButtonStyle.Add(switcherButtonStyle); } Add(leafAndHideButtonStyle);