diff --git a/src/AtomUI.Controls/Breadcrumb/Breadcrumb.cs b/src/AtomUI.Controls/Breadcrumb/Breadcrumb.cs index 74c49e8fc..9965a530c 100644 --- a/src/AtomUI.Controls/Breadcrumb/Breadcrumb.cs +++ b/src/AtomUI.Controls/Breadcrumb/Breadcrumb.cs @@ -1,53 +1,43 @@ -using System.Collections; -using System.Collections.Specialized; +using System.Collections.Specialized; +using AtomUI.Data; using AtomUI.Theme; using AtomUI.Theme.Utils; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Threading; -using Avalonia.VisualTree; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Metadata; namespace AtomUI.Controls; public class Breadcrumb : ItemsControl, IControlSharedTokenResourcesHost, IMotionAwareControl { #region 公共属性定义 - - public static readonly StyledProperty ParamsProperty = - AvaloniaProperty.Register( - nameof(Params)); - - public static readonly StyledProperty IconSizeProperty = - AvaloniaProperty.Register(nameof(IconSize), defaultValue: 16); - - public static readonly StyledProperty SeparatorProperty = - AvaloniaProperty.Register( + public static readonly StyledProperty SeparatorProperty = + AvaloniaProperty.Register( nameof(Separator), defaultValue: "/" ); + + public static readonly StyledProperty SeparatorTemplateProperty = + AvaloniaProperty.Register(nameof (SeparatorTemplate)); public static readonly StyledProperty IsMotionEnabledProperty = MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); - public IEnumerable? Params - { - get => GetValue(ParamsProperty); - set => SetValue(ParamsProperty, value); - } - - public double IconSize - { - get => GetValue(IconSizeProperty); - set => SetValue(IconSizeProperty, value); - } - - public string? Separator + [DependsOn("ContentTemplate")] + public object? Separator { get => GetValue(SeparatorProperty); set => SetValue(SeparatorProperty, value); } - + + public IDataTemplate? SeparatorTemplate + { + get => GetValue(SeparatorTemplateProperty); + set => SetValue(SeparatorTemplateProperty, value); + } + public bool IsMotionEnabled { get => GetValue(IsMotionEnabledProperty); @@ -56,47 +46,62 @@ public class Breadcrumb : ItemsControl, IControlSharedTokenResourcesHost, IMotio #endregion + #region 公共事件定义 + + public event EventHandler? NavigateRequest; + + #endregion + Control IMotionAwareControl.PropertyBindTarget => this; Control IControlSharedTokenResourcesHost.HostControl => this; string IControlSharedTokenResourcesHost.TokenId => BreadcrumbToken.ID; public Breadcrumb() { - Items.CollectionChanged += OnItemsCollectionChanged; + Items.CollectionChanged += HandleItemsCollectionChanged; this.RegisterResources(); - this.BindMotionProperties(); } - private void OnItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + private void HandleItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - // parse a property from axaml is async in Avalonia. So use Dispatcher.UIThread to make sure object property has a value - Dispatcher.UIThread.Post(() => + if (Items.Count > 0) { - this._processChildItems(); - }, DispatcherPriority.Background); - } - - private void _processChildItems() - { - if (Items != null && Items.Count > 0) - { - var lastChildItem = Items[Items.Count - 1]; - foreach (var childItem in Items) + for (int i = 0; i < ItemCount; i++) { - if (childItem is BreadcrumbItem breadcrumbItem) + var item = Items[i]; + if (item is BreadcrumbItem breadcrumbItem) { - breadcrumbItem.IsLast = false; - breadcrumbItem.EffectiveSeparator = ((breadcrumbItem.Separator ?? this.Separator)) ?? "/"; - breadcrumbItem.Content = breadcrumbItem.Value != null ? breadcrumbItem.Value.ToString() : breadcrumbItem.Content ; + breadcrumbItem.IsLast = (i == ItemCount - 1); } } - if (lastChildItem is BreadcrumbItem lastBreadcrumbItem) - { - lastBreadcrumbItem.IsLast = true; - lastBreadcrumbItem.EffectiveSeparator = lastBreadcrumbItem.IsLast - ? string.Empty - : ((lastBreadcrumbItem.Separator ?? this.Separator)) ?? string.Empty; - } } } + + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) + { + var breadcrumbItem = new BreadcrumbItem(); + // 避免破环 BreadcrumbItem 自行指定的分割符 + BindUtils.RelayBind(this, SeparatorProperty, breadcrumbItem, BreadcrumbItem.SeparatorProperty, BindingMode.Default, BindingPriority.Template); + BindUtils.RelayBind(this, SeparatorTemplateProperty, breadcrumbItem, BreadcrumbItem.SeparatorTemplateProperty, BindingMode.Default, BindingPriority.Template); + return breadcrumbItem; + } + + protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) + { + return NeedsContainer(item, out recycleKey); + } + + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is BreadcrumbItem breadcrumbItem) + { + breadcrumbItem[!BreadcrumbItem.IsMotionEnabledProperty] = this[!IsMotionEnabledProperty]; + } + } + + internal void NotifyNavigateRequest(BreadcrumbItem breadcrumbItem) + { + NavigateRequest?.Invoke(this, new BreadcrumbNavigateEventArgs(breadcrumbItem)); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Breadcrumb/BreadcrumbItem.cs b/src/AtomUI.Controls/Breadcrumb/BreadcrumbItem.cs index 2e8f262b3..bd090b4d9 100644 --- a/src/AtomUI.Controls/Breadcrumb/BreadcrumbItem.cs +++ b/src/AtomUI.Controls/Breadcrumb/BreadcrumbItem.cs @@ -1,68 +1,77 @@ -using AtomUI.Animations; +using System.Diagnostics; +using AtomUI.Animations; using AtomUI.Controls.Utils; using AtomUI.IconPkg; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Metadata; -using Avalonia.Controls.Mixins; -using Avalonia.Controls.Primitives; using Avalonia.Animation; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.LogicalTree; +using Avalonia.Metadata; +using Avalonia.Threading; using Avalonia.VisualTree; namespace AtomUI.Controls; -[PseudoClasses(StdPseudoClass.Pressed, StdPseudoClass.Selected, BreadcrumbPseudoClass.HasIcon, BreadcrumbPseudoClass.IsLast)] -public class BreadcrumbItem : ContentControl, ISelectable +using AvaloniaButton = Avalonia.Controls.Button; + +[PseudoClasses(StdPseudoClass.Pressed, BreadcrumbPseudoClass.HasIcon, BreadcrumbPseudoClass.IsLast)] +public class BreadcrumbItem : AvaloniaButton { - #region 公共属性定义 - - public static readonly StyledProperty SeparatorProperty = - AvaloniaProperty.Register(nameof(Separator)); + public static readonly StyledProperty IconProperty = + AvaloniaProperty.Register(nameof(Icon)); - public static readonly StyledProperty ValueProperty = - AvaloniaProperty.Register(nameof(Value)); + public static readonly StyledProperty NavigateContextProperty = + AvaloniaProperty.Register(nameof(NavigateContext)); - public static readonly StyledProperty IsSelectedProperty = - SelectingItemsControl.IsSelectedProperty.AddOwner(); - - public static readonly StyledProperty IconProperty - = AvaloniaProperty.Register(nameof(Icon)); + public static readonly StyledProperty NavigateUriProperty = + AvaloniaProperty.Register( + nameof(NavigateUri), + defaultValue: null); - public string? Separator + public static readonly StyledProperty SeparatorProperty = + Breadcrumb.SeparatorProperty.AddOwner(); + + public static readonly StyledProperty SeparatorTemplateProperty = + Breadcrumb.SeparatorTemplateProperty.AddOwner(); + + public object? NavigateContext { - get => GetValue(SeparatorProperty); - set => SetValue(SeparatorProperty, value); - } - - public string? Value - { - get => GetValue(ValueProperty); - set => SetValue(ValueProperty, value); + get => GetValue(NavigateContextProperty); + set => SetValue(NavigateContextProperty, value); } - public bool IsSelected - { - get => GetValue(IsSelectedProperty); - set => SetValue(IsSelectedProperty, value); - } - public Icon? Icon { get => GetValue(IconProperty); set => SetValue(IconProperty, value); } + public Uri? NavigateUri + { + get => GetValue(NavigateUriProperty); + set => SetValue(NavigateUriProperty, value); + } + + [DependsOn("ContentTemplate")] + public object? Separator + { + get => GetValue(SeparatorProperty); + set => SetValue(SeparatorProperty, value); + } + + public IDataTemplate? SeparatorTemplate + { + get => GetValue(SeparatorTemplateProperty); + set => SetValue(SeparatorTemplateProperty, value); + } #endregion #region 内部属性定义 - internal static readonly DirectProperty EffectiveSeparatorProperty = - AvaloniaProperty.RegisterDirect( - nameof(EffectiveSeparator), - o => o.EffectiveSeparator, - (o, v) => o.EffectiveSeparator = v); - internal static readonly StyledProperty IsMotionEnabledProperty = MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); @@ -72,13 +81,12 @@ public class BreadcrumbItem : ContentControl, ISelectable o => o.IsLast, (o, v) => o.IsLast = v); - private string _effectiveSeparator = string.Empty; - internal string EffectiveSeparator - { - get => _effectiveSeparator; - set => SetAndRaise(EffectiveSeparatorProperty, ref _effectiveSeparator, value); - } - + internal static readonly DirectProperty IsNavigateResponsiveProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsNavigateResponsive), + o => o.IsNavigateResponsive, + (o, v) => o.IsNavigateResponsive = v); + internal bool IsMotionEnabled { get => GetValue(IsMotionEnabledProperty); @@ -92,21 +100,34 @@ public class BreadcrumbItem : ContentControl, ISelectable set => SetAndRaise(IsLastProperty, ref _isLast, value); } + private bool _isNavigateResponsive; + internal bool IsNavigateResponsive + { + get => _isNavigateResponsive; + set => SetAndRaise(IsNavigateResponsiveProperty, ref _isNavigateResponsive, value); + } #endregion - static BreadcrumbItem() + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { - AffectsRender(BackgroundProperty); + base.OnAttachedToLogicalTree(e); + Debug.Assert(Parent is Breadcrumb, "BreadcrumbItem's Parent must be Breadcrumb Control."); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - if (change.Property == IconProperty || change.Property == IsLastProperty) + if (change.Property == IconProperty || + change.Property == IsLastProperty) { UpdatePseudoClasses(); } + else if (change.Property == NavigateUriProperty || + change.Property == NavigateContextProperty) + { + IsNavigateResponsive = NavigateUri != null || NavigateContext != null; + } if (this.IsAttachedToVisualTree()) { @@ -116,6 +137,13 @@ public class BreadcrumbItem : ContentControl, ISelectable } } } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + ConfigureTransitions(); + UpdatePseudoClasses(); + } private void ConfigureTransitions() { @@ -123,6 +151,7 @@ public class BreadcrumbItem : ContentControl, ISelectable { Transitions = new Transitions { + TransitionUtils.CreateTransition(ForegroundProperty), TransitionUtils.CreateTransition(BackgroundProperty) }; } @@ -137,4 +166,24 @@ public class BreadcrumbItem : ContentControl, ISelectable PseudoClasses.Set(BreadcrumbPseudoClass.HasIcon, Icon is not null); PseudoClasses.Set(BreadcrumbPseudoClass.IsLast, IsLast); } + + protected override void OnClick() + { + base.OnClick(); + if (IsNavigateResponsive) + { + if (Parent is Breadcrumb breadcrumb) + { + breadcrumb.NotifyNavigateRequest(this); + } + Uri? uri = NavigateUri; + if (uri is not null) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + await TopLevel.GetTopLevel(this)!.Launcher.LaunchUriAsync(uri); + }); + } + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Breadcrumb/BreadcrumbNavigateEventArgs.cs b/src/AtomUI.Controls/Breadcrumb/BreadcrumbNavigateEventArgs.cs new file mode 100644 index 000000000..60127704d --- /dev/null +++ b/src/AtomUI.Controls/Breadcrumb/BreadcrumbNavigateEventArgs.cs @@ -0,0 +1,11 @@ +namespace AtomUI.Controls; + +public class BreadcrumbNavigateEventArgs : EventArgs +{ + public BreadcrumbItem BreadcrumbItem { get; } + + public BreadcrumbNavigateEventArgs(BreadcrumbItem breadcrumbItem) + { + BreadcrumbItem = breadcrumbItem; + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Breadcrumb/BreadcrumbStackPanel.cs b/src/AtomUI.Controls/Breadcrumb/BreadcrumbStackPanel.cs deleted file mode 100644 index d02148596..000000000 --- a/src/AtomUI.Controls/Breadcrumb/BreadcrumbStackPanel.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Avalonia; -using Avalonia.Controls; - -namespace AtomUI.Controls; - -internal class BreadcrumbStackPanel : Panel -{ - protected override Size MeasureOverride(Size availableSize) - { - var targetHeight = 0d; - var targetWidth = 0d; - foreach (var child in Children) - { - if (child is BreadcrumbItem box) - { - box.Measure(availableSize); - targetWidth += box.DesiredSize.Width; - targetHeight = Math.Max(targetHeight, box.DesiredSize.Height); - } - } - - return new Size(targetWidth, targetHeight); - } - - protected override Size ArrangeOverride(Size finalSize) - { - var previousChildSize = 0.0; - var offsetX = 0d; - var offsetY = 0d; - foreach (var child in Children) - { - if (child is BreadcrumbItem box) - { - previousChildSize = box.DesiredSize.Width; - box.Arrange(new Rect(new Point(offsetX, offsetY), box.DesiredSize)); - offsetX += previousChildSize; - } - } - return finalSize; - } -} \ No newline at end of file diff --git a/src/AtomUI.Controls/Breadcrumb/BreadcrumbToken.cs b/src/AtomUI.Controls/Breadcrumb/BreadcrumbToken.cs index 838fbf674..f163a93e5 100644 --- a/src/AtomUI.Controls/Breadcrumb/BreadcrumbToken.cs +++ b/src/AtomUI.Controls/Breadcrumb/BreadcrumbToken.cs @@ -8,7 +8,7 @@ namespace AtomUI.Controls; internal class BreadcrumbToken : AbstractControlDesignToken { public const string ID = "Breadcrumb"; - + /// /// 图标大小 /// @@ -18,37 +18,27 @@ internal class BreadcrumbToken : AbstractControlDesignToken /// 面包屑项文字颜色 /// public Color ItemColor { get; set; } - + /// /// 最后一项文字颜色 /// public Color LastItemColor { get; set; } - + /// /// 链接文字颜色 /// public Color LinkColor { get; set; } - + /// /// 链接文字悬浮颜色 /// public Color LinkHoverColor { get; set; } - /// - /// 非最后一个面包屑导航的颜色 - /// - public Color PrevLinkColor { get; set; } - /// /// 链接文字悬浮背景颜色 /// public Color LinkHoverBgColor { get; set; } - /// - /// BreadcrumbItem之间的margin - /// - public Thickness BreadcrumbItemContentMargin { get; set; } - /// /// BreadcrumbItem外面Border的Padding /// @@ -58,31 +48,29 @@ internal class BreadcrumbToken : AbstractControlDesignToken /// 分隔符颜色 /// public Color SeparatorColor { get; set; } - + /// /// 分隔符外间距 /// public Thickness SeparatorMargin { get; set; } - + public BreadcrumbToken() : base(ID) { } - + protected internal override void CalculateFromAlias() { base.CalculateFromAlias(); - IconSize = SharedToken.IconSize; - var universalColor = SharedToken.ColorText; - ItemColor = universalColor; - LastItemColor = universalColor; - LinkColor = universalColor; - PrevLinkColor = SharedToken.ColorTextDisabled; + IconSize = SharedToken.IconSize; + ItemColor = SharedToken.ColorTextDescription; + LastItemColor = SharedToken.ColorText; + LinkColor = SharedToken.ColorTextDescription; LinkHoverColor = SharedToken.ColorText; LinkHoverBgColor = SharedToken.ColorBgTextHover; - SeparatorColor = universalColor; - SeparatorMargin = SharedToken.MarginMD; - BreadcrumbItemContentMargin = new Thickness(SharedToken.UniformlyPaddingXXS, 0, 0, 0); - BreadcrumbItemContentPadding = new Thickness(SharedToken.UniformlyPaddingXXS, SharedToken.UniformlyPaddingXXS, SharedToken.UniformlyPaddingXXS, SharedToken.UniformlyPaddingXXS); + SeparatorColor = SharedToken.ColorTextDescription; + SeparatorMargin = new Thickness(SharedToken.UniformlyMarginXXS, 0); + BreadcrumbItemContentPadding = new Thickness(SharedToken.UniformlyPaddingXXS, SharedToken.UniformlyPaddingXXS, + SharedToken.UniformlyPaddingXXS, SharedToken.UniformlyPaddingXXS); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbItemTheme.axaml b/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbItemTheme.axaml index 3dcacb02c..d6f5bec0f 100644 --- a/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbItemTheme.axaml +++ b/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbItemTheme.axaml @@ -2,91 +2,69 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:atom="https://atomui.net"> - - - - + - - + + - - - - - - + - - + + + + - - - - - - - - - - - + + \ No newline at end of file diff --git a/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbTheme.axaml b/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbTheme.axaml index 23562bbb9..ecbb84ee1 100644 --- a/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbTheme.axaml +++ b/src/AtomUI.Controls/Breadcrumb/Themes/BreadcrumbTheme.axaml @@ -2,29 +2,22 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:atom="https://atomui.net"> - - + - - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs index 9429d6337..f8d17c386 100644 --- a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs +++ b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs @@ -90,7 +90,6 @@ namespace AtomUI.Theme.Styling public static class BreadcrumbTokenKey { - public static readonly TokenResourceKey BreadcrumbItemContentMargin = new TokenResourceKey("Breadcrumb.BreadcrumbItemContentMargin"); public static readonly TokenResourceKey BreadcrumbItemContentPadding = new TokenResourceKey("Breadcrumb.BreadcrumbItemContentPadding"); public static readonly TokenResourceKey IconSize = new TokenResourceKey("Breadcrumb.IconSize"); public static readonly TokenResourceKey ItemColor = new TokenResourceKey("Breadcrumb.ItemColor"); @@ -98,23 +97,10 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey LinkColor = new TokenResourceKey("Breadcrumb.LinkColor"); public static readonly TokenResourceKey LinkHoverBgColor = new TokenResourceKey("Breadcrumb.LinkHoverBgColor"); public static readonly TokenResourceKey LinkHoverColor = new TokenResourceKey("Breadcrumb.LinkHoverColor"); - public static readonly TokenResourceKey PrevLinkColor = new TokenResourceKey("Breadcrumb.PrevLinkColor"); public static readonly TokenResourceKey SeparatorColor = new TokenResourceKey("Breadcrumb.SeparatorColor"); public static readonly TokenResourceKey SeparatorMargin = new TokenResourceKey("Breadcrumb.SeparatorMargin"); } - public static class ButtonSpinnerTokenKey - { - public static readonly TokenResourceKey ControlWidth = new TokenResourceKey("ButtonSpinner.ControlWidth"); - public static readonly TokenResourceKey FilledHandleBg = new TokenResourceKey("ButtonSpinner.FilledHandleBg"); - public static readonly TokenResourceKey HandleActiveBg = new TokenResourceKey("ButtonSpinner.HandleActiveBg"); - public static readonly TokenResourceKey HandleBg = new TokenResourceKey("ButtonSpinner.HandleBg"); - public static readonly TokenResourceKey HandleBorderColor = new TokenResourceKey("ButtonSpinner.HandleBorderColor"); - public static readonly TokenResourceKey HandleHoverColor = new TokenResourceKey("ButtonSpinner.HandleHoverColor"); - public static readonly TokenResourceKey HandleIconSize = new TokenResourceKey("ButtonSpinner.HandleIconSize"); - public static readonly TokenResourceKey HandleWidth = new TokenResourceKey("ButtonSpinner.HandleWidth"); - } - public static class ButtonTokenKey { public static readonly TokenResourceKey BorderColorDisabled = new TokenResourceKey("Button.BorderColorDisabled");