diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml
index 2134d7294..32ddbdd9d 100644
--- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml
+++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml
@@ -31,5 +31,29 @@
+
+
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+
+
+
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+
+
\ 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 12686a5ee..5a370f47d 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
@@ -314,6 +314,7 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey ItemHoverColor = new TokenResourceKey("TabControl.ItemHoverColor");
public static readonly TokenResourceKey ItemSelectedColor = new TokenResourceKey("TabControl.ItemSelectedColor");
public static readonly TokenResourceKey CardGutter = new TokenResourceKey("TabControl.CardGutter");
+ public static readonly TokenResourceKey ItemIconMargin = new TokenResourceKey("TabControl.ItemIconMargin");
}
public static class TagResourceKey
diff --git a/src/AtomUI.Controls/PathIcon/PathIcon.cs b/src/AtomUI.Controls/PathIcon/PathIcon.cs
index d1af9821a..2222ff0b4 100644
--- a/src/AtomUI.Controls/PathIcon/PathIcon.cs
+++ b/src/AtomUI.Controls/PathIcon/PathIcon.cs
@@ -2,6 +2,7 @@
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
+using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Reactive;
using Avalonia.Rendering;
@@ -145,6 +146,8 @@ public sealed class PathIcon : Control, ICustomHitTest
FilledBrushProperty,
PrimaryFilledBrushProperty,
SecondaryFilledBrushProperty);
+ HorizontalAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Left);
+ VerticalAlignmentProperty.OverrideDefaultValue(VerticalAlignment.Top);
}
public PathIcon()
diff --git a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs
index 6e9af577d..9f9fe88d3 100644
--- a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs
+++ b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs
@@ -1,5 +1,4 @@
-using AtomUI.Theme.Data;
-using AtomUI.Theme.Styling;
+using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
@@ -29,6 +28,9 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware
public static readonly StyledProperty TabStripPlacementProperty =
AvaloniaProperty.Register(nameof(TabStripPlacement), defaultValue: Dock.Top);
+
+ public static readonly StyledProperty TabAlignmentCenterProperty =
+ AvaloniaProperty.Register(nameof(TabAlignmentCenter), false);
public SizeType SizeType
{
@@ -42,6 +44,12 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware
set => SetValue(TabStripPlacementProperty, value);
}
+ public bool TabAlignmentCenter
+ {
+ get => GetValue(TabAlignmentCenterProperty);
+ set => SetValue(TabAlignmentCenterProperty, value);
+ }
+
#endregion
private Border? _mainContainer;
diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs
index 38ebc2b5a..a9a11205e 100644
--- a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs
+++ b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs
@@ -1,4 +1,5 @@
-using AtomUI.Theme;
+using AtomUI.Icon;
+using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
@@ -14,6 +15,7 @@ internal class BaseTabStripItemTheme : BaseControlTheme
public const string DecoratorPart = "Part_Decorator";
public const string ContentLayoutPart = "Part_ContentLayout";
public const string ContentPresenterPart = "PART_ContentPresenter";
+ public const string ItemIconPart = "PART_ItemIcon";
public BaseTabStripItemTheme(Type targetType) : base(targetType) { }
@@ -62,16 +64,26 @@ internal class BaseTabStripItemTheme : BaseControlTheme
commonStyle.Add(TabStripItem.CursorProperty, new Cursor(StandardCursorType.Hand));
commonStyle.Add(TabStripItem.ForegroundProperty, TabControlResourceKey.ItemColor);
- // 增加文字 hover 和 选中的效果
+ // hover
var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TabStripItem.ForegroundProperty, TabControlResourceKey.ItemHoverColor);
+ {
+ var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
+ iconStyle.Add(PathIcon.IconModeProperty, IconMode.Active);
+ hoverStyle.Add(iconStyle);
+ }
+
commonStyle.Add(hoverStyle);
// 选中
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
selectedStyle.Add(TabStripItem.ForegroundProperty, TabControlResourceKey.ItemSelectedColor);
+ {
+ var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
+ iconStyle.Add(PathIcon.IconModeProperty, IconMode.Selected);
+ selectedStyle.Add(iconStyle);
+ }
commonStyle.Add(selectedStyle);
-
Add(commonStyle);
BuildSizeTypeStyle();
BuildDisabledStyle();
diff --git a/src/AtomUI.Controls/TabControl/TabControlToken.cs b/src/AtomUI.Controls/TabControl/TabControlToken.cs
index d040482a6..f0db77d18 100644
--- a/src/AtomUI.Controls/TabControl/TabControlToken.cs
+++ b/src/AtomUI.Controls/TabControl/TabControlToken.cs
@@ -116,6 +116,11 @@ internal class TabControlToken : AbstractControlDesignToken
/// 卡片标签间距
///
public double CardGutter { get; set; }
+
+ ///
+ /// 标签内容 icon 的外边距
+ ///
+ public Thickness ItemIconMargin { get; set; }
internal override void CalculateFromAlias()
{
@@ -152,5 +157,6 @@ internal class TabControlToken : AbstractControlDesignToken
ItemHoverColor = colorToken.ColorPrimaryToken.ColorPrimaryHover;
CardGutter = _globalToken.MarginXXS / 2;
+ ItemIconMargin = new Thickness(0, 0, _globalToken.MarginSM, 0);
}
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/TabControl/TabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip.cs
index 517267222..77f30bd6f 100644
--- a/src/AtomUI.Controls/TabControl/TabStrip.cs
+++ b/src/AtomUI.Controls/TabControl/TabStrip.cs
@@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Media.Transformation;
@@ -12,10 +13,12 @@ namespace AtomUI.Controls;
public class TabStrip : BaseTabStrip
{
private Border? _selectedIndicator;
+ private ItemsPresenter? _itemsPresenter;
public TabStrip()
{
SelectionChanged += HandleSelectionChanged;
+ LayoutUpdated += HandleLayoutUpdated;
}
private void HandleSelectionChanged(object? sender, SelectionChangedEventArgs args)
@@ -25,15 +28,29 @@ public class TabStrip : BaseTabStrip
}
}
+ private void HandleLayoutUpdated(object? sender, EventArgs args)
+ {
+ if (_selectedIndicator is not null) {
+ if (_selectedIndicator.Transitions is null) {
+ var transitions = new Transitions();
+ transitions.Add(AnimationUtils.CreateTransition(Border.RenderTransformProperty,
+ GlobalResourceKey.MotionDurationSlow, new ExponentialEaseOut()));
+ _selectedIndicator.Transitions = transitions;
+ // 只需要执行一次
+ LayoutUpdated -= HandleLayoutUpdated;
+ }
+ }
+ }
+
private void SetupSelectedIndicator()
{
if (_selectedIndicator is not null && SelectedItem is TabStripItem tabStripItem) {
var selectedBounds = tabStripItem.Bounds;
var builder = new TransformOperations.Builder(1);
-
+ var offset = _itemsPresenter?.Bounds.Position ?? default;
if (TabStripPlacement == Dock.Top) {
_selectedIndicator.Width = tabStripItem.DesiredSize.Width;
- builder.AppendTranslate(selectedBounds.Left, 0);
+ builder.AppendTranslate(offset.X + selectedBounds.Left, 0);
} else if (TabStripPlacement == Dock.Right) {
_selectedIndicator.Height = tabStripItem.DesiredSize.Height;
} else if (TabStripPlacement == Dock.Bottom) {
@@ -45,9 +62,9 @@ public class TabStrip : BaseTabStrip
}
}
- protected override Size MeasureOverride(Size availableSize)
+ protected override Size ArrangeOverride(Size finalSize)
{
- var size = base.MeasureOverride(availableSize);
+ var size = base.ArrangeOverride(finalSize);
if (SelectedItem is TabStripItem) {
SetupSelectedIndicator();
}
@@ -56,7 +73,7 @@ public class TabStrip : BaseTabStrip
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
- var tabStripItem = new TabStripItem()
+ var tabStripItem = new TabStripItem
{
Shape = TabSharp.Line
};
@@ -67,13 +84,6 @@ public class TabStrip : BaseTabStrip
{
base.OnApplyTemplate(e);
_selectedIndicator = e.NameScope.Find(TabStripTheme.SelectedItemIndicatorPart);
- if (_selectedIndicator is not null) {
- if (_selectedIndicator.Transitions is null) {
- var transitions = new Transitions();
- transitions.Add(AnimationUtils.CreateTransition(Border.RenderTransformProperty,
- GlobalResourceKey.MotionDurationSlow, new ExponentialEaseOut()));
- _selectedIndicator.Transitions = transitions;
- }
- }
+ _itemsPresenter = e.NameScope.Find(TabStripTheme.ItemsPresenterPart);
}
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/TabControl/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStripItem.cs
index 11ecdb0a5..0e2109e07 100644
--- a/src/AtomUI.Controls/TabControl/TabStripItem.cs
+++ b/src/AtomUI.Controls/TabControl/TabStripItem.cs
@@ -1,4 +1,5 @@
using AtomUI.Controls.Utils;
+using AtomUI.Icon;
using AtomUI.Media;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
@@ -8,6 +9,7 @@ using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
+using Avalonia.Rendering;
namespace AtomUI.Controls;
@@ -19,7 +21,7 @@ public enum TabSharp
Card
}
-public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle
+public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHitTest
{
#region 公共属性定义
public static readonly StyledProperty SizeTypeProperty =
@@ -92,6 +94,16 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle
{
if (Icon is not null) {
UIStructureUtils.SetTemplateParent(Icon, this);
+ Icon.Name = TabStripItemTheme.ItemIconPart;
+ if (Icon.ThemeType != IconThemeType.TwoTone) {
+ TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor);
+ TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor);
+ TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.SelectedFilledBrushProperty, TabControlResourceKey.ItemSelectedColor);
+ TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled);
+ }
+ if (_contentLayout is not null) {
+ _contentLayout.Children.Insert(0, Icon);
+ }
}
}
@@ -124,9 +136,8 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle
if (oldIcon != null) {
UIStructureUtils.SetTemplateParent(oldIcon, null);
}
- if (Icon is not null) {
- UIStructureUtils.SetTemplateParent(Icon, this);
- }
+
+ SetupItemIcon();
}
}
}
@@ -140,4 +151,9 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle
TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, CardTabStripItemTheme.ID);
}
}
+
+ public bool HitTest(Point point)
+ {
+ return true;
+ }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs
index 4ceea5893..89e207f8d 100644
--- a/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs
+++ b/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs
@@ -1,5 +1,6 @@
using AtomUI.Theme.Styling;
using Avalonia.Controls;
+using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
@@ -19,10 +20,19 @@ internal class TabStripItemTheme : BaseTabStripItemTheme
protected override void BuildStyles()
{
base.BuildStyles();
+
+ // Icon 一些通用属性
+ {
+ var iconStyle = new Style(selector => selector.Nesting().Template().OfType());
+ iconStyle.Add(PathIcon.MarginProperty, TabControlResourceKey.ItemIconMargin);
+ Add(iconStyle);
+ }
+
var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType().Name(DecoratorPart));
decoratorStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin);
Add(decoratorStyle);
BuildSizeTypeStyle();
+ BuildPlacementStyle();
}
protected void BuildSizeTypeStyle()
@@ -33,6 +43,13 @@ internal class TabStripItemTheme : BaseTabStripItemTheme
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingLG);
largeSizeStyle.Add(decoratorStyle);
}
+ {
+ // Icon
+ var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
+ iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
+ iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
+ largeSizeStyle.Add(iconStyle);
+ }
Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle));
@@ -41,6 +58,13 @@ internal class TabStripItemTheme : BaseTabStripItemTheme
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
+ {
+ // Icon
+ var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
+ iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
+ iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
+ middleSizeStyle.Add(iconStyle);
+ }
Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small));
@@ -49,6 +73,53 @@ internal class TabStripItemTheme : BaseTabStripItemTheme
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingSM);
smallSizeType.Add(decoratorStyle);
}
+ {
+ // Icon
+ var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
+ iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM);
+ iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM);
+ smallSizeType.Add(iconStyle);
+ }
Add(smallSizeType);
}
+
+ private void BuildPlacementStyle()
+ {
+ // 设置 items presenter 面板样式
+ // 分为上、右、下、左
+ {
+ // 上
+ var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top));
+ var iconStyle = new Style(selector => selector.Nesting().Template().OfType());
+ iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
+ topStyle.Add(iconStyle);
+ Add(topStyle);
+ }
+
+ {
+ // 右
+ var rightStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right));
+ var iconStyle = new Style(selector => selector.Nesting().Template().OfType());
+ iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
+ rightStyle.Add(iconStyle);
+ Add(rightStyle);
+ }
+ {
+ // 下
+ var bottomStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom));
+
+ var iconStyle = new Style(selector => selector.Nesting().Template().OfType());
+ iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
+ bottomStyle.Add(iconStyle);
+ Add(bottomStyle);
+ }
+ {
+ // 左
+ var leftStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left));
+ var iconStyle = new Style(selector => selector.Nesting().Template().OfType());
+ iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
+ leftStyle.Add(iconStyle);
+ Add(leftStyle);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/TabControl/TabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStripTheme.cs
index 0d141d76d..1d2dea731 100644
--- a/src/AtomUI.Controls/TabControl/TabStripTheme.cs
+++ b/src/AtomUI.Controls/TabControl/TabStripTheme.cs
@@ -18,7 +18,7 @@ internal class TabStripTheme : BaseTabStripTheme
protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container)
{
var layout = new Panel();
- var itemsPresenter = new ItemsPresenter()
+ var itemsPresenter = new ItemsPresenter
{
Name = ItemsPresenterPart,
};
@@ -55,6 +55,13 @@ internal class TabStripTheme : BaseTabStripTheme
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom);
topStyle.Add(indicatorStyle);
+ // tabs 是否居中
+ var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
+ var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
+ itemsPresenterStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
+ tabAlignCenterStyle.Add(itemsPresenterStyle);
+ topStyle.Add(tabAlignCenterStyle);
+
topStyle.Add(itemPresenterPanelStyle);
commonStyle.Add(topStyle);
}
@@ -72,6 +79,13 @@ internal class TabStripTheme : BaseTabStripTheme
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
rightStyle.Add(indicatorStyle);
+ // tabs 是否居中
+ var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
+ var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
+ itemsPresenterStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
+ tabAlignCenterStyle.Add(itemsPresenterStyle);
+ rightStyle.Add(tabAlignCenterStyle);
+
commonStyle.Add(rightStyle);
}
{
@@ -87,6 +101,13 @@ internal class TabStripTheme : BaseTabStripTheme
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
bottomStyle.Add(indicatorStyle);
+ // tabs 是否居中
+ var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
+ var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
+ itemsPresenterStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
+ tabAlignCenterStyle.Add(itemsPresenterStyle);
+ bottomStyle.Add(tabAlignCenterStyle);
+
commonStyle.Add(bottomStyle);
}
{
@@ -102,6 +123,13 @@ internal class TabStripTheme : BaseTabStripTheme
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
leftStyle.Add(indicatorStyle);
+ // tabs 是否居中
+ var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
+ var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
+ itemsPresenterStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
+ tabAlignCenterStyle.Add(itemsPresenterStyle);
+ leftStyle.Add(tabAlignCenterStyle);
+
commonStyle.Add(leftStyle);
}