From ce2123d9d659fea03a0e5b594a8dd73166fbcd5d Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 10:49:14 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E5=AE=8C=E6=88=90card=E9=A3=8E=E6=A0=BC?= =?UTF-8?q?=E7=9A=84tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCase/TabControlShowCase.axaml | 158 ++++++++++-------- src/AtomUI.Controls/Buttons/IconButton.cs | 3 + .../TokenResourceConst.g.cs | 1 + .../TabControl/BaseTabStrip.cs | 7 +- .../TabControl/BaseTabStripItemTheme.cs | 81 ++++++++- .../TabControl/BaseTabStripTheme.cs | 59 +++++++ .../TabControl/CardTabStrip.cs | 8 + .../TabControl/CardTabStripItemTheme.cs | 123 +++++++++++++- .../TabControl/CardTabStripTheme.cs | 62 +++++++ .../TabControl/TabControlToken.cs | 11 ++ src/AtomUI.Controls/TabControl/TabStrip.cs | 8 + .../TabControl/TabStripItem.cs | 108 +++++++++++- .../TabControl/TabStripItemTheme.cs | 79 +-------- .../TabControl/TabStripTheme.cs | 46 +---- 14 files changed, 562 insertions(+), 192 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 36b1bdc..0e02f32 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -8,83 +8,97 @@ xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase" mc:Ignorable="d"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title="Card type tab" + Description="Another type of Tabs, which doesn't support vertical mode."> - - Tab 1 - Tab 2 - Tab 3 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - - - - - - - + Tab 1 Tab 2 Tab 3 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - Tab 5 - Tab 6 - Tab 7 - Tab 8 - Tab 9 - Tab 10 - Tab 11 - Tab 12 - Tab 13 - Tab 14 - Tab 15 - Tab 16 - Tab 17 - Tab 18 - Tab 19 - Tab 20 - Tab 21 - Tab 22 - Tab 23 - + Tab 4 + + diff --git a/src/AtomUI.Controls/Buttons/IconButton.cs b/src/AtomUI.Controls/Buttons/IconButton.cs index 5dd2754..3e082ee 100644 --- a/src/AtomUI.Controls/Buttons/IconButton.cs +++ b/src/AtomUI.Controls/Buttons/IconButton.cs @@ -2,6 +2,7 @@ using AtomUI.Icon; using AtomUI.Theme.Styling; using Avalonia; using Avalonia.Input; +using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Rendering; @@ -39,6 +40,8 @@ public class IconButton : AvaloniaButton, ICustomHitTest base.OnAttachedToLogicalTree(e); if (!_initialized) { if (Icon is not null) { + Icon.VerticalAlignment = VerticalAlignment.Center; + Icon.HorizontalAlignment = HorizontalAlignment.Center; Content = Icon; } _initialized = true; 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 ef06059..f7363cc 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 @@ -318,6 +318,7 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey MenuIndicatorPaddingHorizontal = new TokenResourceKey("TabControl.MenuIndicatorPaddingHorizontal"); public static readonly TokenResourceKey MenuIndicatorPaddingVertical = new TokenResourceKey("TabControl.MenuIndicatorPaddingVertical"); public static readonly TokenResourceKey MenuEdgeThickness = new TokenResourceKey("TabControl.MenuEdgeThickness"); + public static readonly TokenResourceKey CloseIconMargin = new TokenResourceKey("TabControl.CloseIconMargin"); } public static class TagResourceKey diff --git a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs index 9f9fe88..a507a77 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs @@ -1,9 +1,11 @@ -using AtomUI.Theme.Styling; +using AtomUI.Theme.Data; +using AtomUI.Theme.Styling; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; @@ -70,7 +72,8 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware private void SetupBorderBinding() { if (_mainContainer is not null) { - TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness); + TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, + new RenderScaleAwareThicknessConfigure(this)); } } diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs index a9a1120..81ccc80 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs @@ -1,6 +1,8 @@ using AtomUI.Icon; using AtomUI.Theme; using AtomUI.Theme.Styling; +using AtomUI.Utils; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; @@ -50,9 +52,13 @@ internal class BaseTabStripItemTheme : BaseControlTheme containerLayout.Children.Add(contentPresenter); CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, TabStripItem.ContentProperty); CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, TabStripItem.ContentTemplateProperty); - + var iconButton = new IconButton(); + TokenResourceBinder.CreateTokenBinding(iconButton, IconButton.MarginProperty, TabControlResourceKey.CloseIconMargin); + CreateTemplateParentBinding(iconButton, IconButton.IconProperty, TabStripItem.CloseIconProperty); + CreateTemplateParentBinding(iconButton, IconButton.IsVisibleProperty, TabStripItem.IsClosableProperty); + containerLayout.Children.Add(iconButton); container.Child = containerLayout; @@ -64,6 +70,13 @@ internal class BaseTabStripItemTheme : BaseControlTheme commonStyle.Add(TabStripItem.CursorProperty, new Cursor(StandardCursorType.Hand)); commonStyle.Add(TabStripItem.ForegroundProperty, TabControlResourceKey.ItemColor); + // Icon 一些通用属性 + { + var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart)); + iconStyle.Add(PathIcon.MarginProperty, TabControlResourceKey.ItemIconMargin); + commonStyle.Add(iconStyle); + } + // hover var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); hoverStyle.Add(TabStripItem.ForegroundProperty, TabControlResourceKey.ItemHoverColor); @@ -86,23 +99,89 @@ internal class BaseTabStripItemTheme : BaseControlTheme commonStyle.Add(selectedStyle); Add(commonStyle); BuildSizeTypeStyle(); + BuildPlacementStyle(); BuildDisabledStyle(); } private void BuildSizeTypeStyle() { var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + largeSizeStyle.Add(TabStripItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeLG); + { + // 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)); + { + // 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); + } middleSizeStyle.Add(TabStripItem.FontSizeProperty, TabControlResourceKey.TitleFontSize); Add(middleSizeStyle); var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); + + { + // 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); + } + smallSizeType.Add(TabStripItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeSM); 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); + } + } private void BuildDisabledStyle() { diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs index d585b7b..0427e22 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs @@ -1,7 +1,9 @@ using AtomUI.Theme; using AtomUI.Theme.Styling; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Layout; using Avalonia.Styling; namespace AtomUI.Controls; @@ -36,6 +38,63 @@ internal class BaseTabStripTheme : BaseControlTheme base.BuildStyles(); var commonStyle = new Style(selector => selector.Nesting()); commonStyle.Add(BaseTabStrip.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + + // 设置 items presenter 是否居中 + // 分为上、右、下、左 + { + // 上 + var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); + + // 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); + + commonStyle.Add(topStyle); + } + + { + // 右 + var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC)); + + // 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); + } + { + // 下 + var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC)); + + // 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); + } + { + // 左 + var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC)); + + // 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); + } + Add(commonStyle); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/CardTabStrip.cs index 6994dec..434d913 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStrip.cs @@ -11,4 +11,12 @@ public class CardTabStrip : BaseTabStrip Shape = TabSharp.Card }; } + + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is TabStripItem tabStripItem) { + tabStripItem.Shape = TabSharp.Card; + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs index 27d3d87..cf59844 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs @@ -1,4 +1,10 @@ -using AtomUI.Theme.Styling; +using AtomUI.Media; +using AtomUI.Theme.Styling; +using AtomUI.Theme.Utils; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Styling; namespace AtomUI.Controls; @@ -13,4 +19,119 @@ internal class CardTabStripItemTheme : BaseTabStripItemTheme { return ID; } + + protected override void NotifyBuildControlTemplate(TabStripItem stripItem, INameScope scope, Border container) + { + base.NotifyBuildControlTemplate(stripItem, scope, container); + + if (container.Transitions is null) { + var transitions = new Transitions(); + transitions.Add(AnimationUtils.CreateTransition(Border.BackgroundProperty)); + container.Transitions = transitions; + } + + + CreateTemplateParentBinding(container, Border.CornerRadiusProperty, TabStripItem.CardBorderRadiusProperty); + } + + protected override void BuildStyles() + { + base.BuildStyles(); + + var commonStyle = new Style(selector => selector.Nesting()); + + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin); + decoratorStyle.Add(Border.BackgroundProperty, TabControlResourceKey.CardBg); + decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + commonStyle.Add(decoratorStyle); + } + + // 选中 + var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainer); + selectedStyle.Add(decoratorStyle); + } + commonStyle.Add(selectedStyle); + + Add(commonStyle); + + BuildSizeTypeStyle(); + BuildPlacementStyle(); + } + + protected void BuildSizeTypeStyle() + { + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingLG); + largeSizeStyle.Add(decoratorStyle); + } + Add(largeSizeStyle); + + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPadding); + middleSizeStyle.Add(decoratorStyle); + } + Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingSM); + smallSizeType.Add(decoratorStyle); + } + + 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); + + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + + 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/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs index f2e4de4..3b9acf0 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs @@ -1,5 +1,8 @@ using AtomUI.Theme.Styling; using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Styling; namespace AtomUI.Controls; @@ -10,6 +13,65 @@ internal class CardTabStripTheme : BaseTabStripTheme protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container) { + var tabScrollViewer = new TabScrollViewer(); + CreateTemplateParentBinding(tabScrollViewer, TabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); + var contentPanel = CreateTabStripContentPanel(scope); + tabScrollViewer.Content = contentPanel; + tabScrollViewer.TabStrip = baseTabStrip; + container.Child = tabScrollViewer; + } + + private ItemsPresenter CreateTabStripContentPanel(INameScope scope) + { + var itemsPresenter = new ItemsPresenter + { + Name = ItemsPresenterPart, + }; + itemsPresenter.RegisterInNameScope(scope); + CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, TabStrip.ItemsPanelProperty); + return itemsPresenter; + } + + protected override void BuildStyles() + { + base.BuildStyles(); + var commonStyle = new Style(selector => selector.Nesting()); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + + commonStyle.Add(itemPresenterPanelStyle); + + // 设置 items presenter 面板样式 + // 分为上、右、下、左 + { + // 上 + var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); + + commonStyle.Add(topStyle); + } + + { + // 右 + var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC)); + + + commonStyle.Add(rightStyle); + } + { + // 下 + var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC)); + + commonStyle.Add(bottomStyle); + } + { + // 左 + var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC)); + + commonStyle.Add(leftStyle); + } + + Add(commonStyle); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabControlToken.cs b/src/AtomUI.Controls/TabControl/TabControlToken.cs index bb95950..2776ca0 100644 --- a/src/AtomUI.Controls/TabControl/TabControlToken.cs +++ b/src/AtomUI.Controls/TabControl/TabControlToken.cs @@ -136,6 +136,11 @@ internal class TabControlToken : AbstractControlDesignToken /// 滚动边缘的厚度 /// public double MenuEdgeThickness { get; set; } + + /// + /// 关闭按钮外边距 + /// + public Thickness CloseIconMargin { get; set; } internal override void CalculateFromAlias() { @@ -146,7 +151,9 @@ internal class TabControlToken : AbstractControlDesignToken var colorToken = _globalToken.ColorToken; CardBg = _globalToken.ColorFillAlter; + CardHeight = _globalToken.HeightToken.ControlHeightLG; + CardPadding = new Thickness(_globalToken.Padding, (CardHeight - Math.Round(_globalToken.FontToken.FontSize * lineHeight)) / 2 - lineWidth); CardPaddingSM = new Thickness(_globalToken.Padding, _globalToken.PaddingXXS * 1.5); CardPaddingLG = new Thickness(top:_globalToken.PaddingXS, @@ -157,7 +164,9 @@ internal class TabControlToken : AbstractControlDesignToken TitleFontSize = fontToken.FontSize; TitleFontSizeLG = fontToken.FontSizeLG; TitleFontSizeSM = fontToken.FontSize; + InkBarColor = colorToken.ColorPrimaryToken.ColorPrimary; + HorizontalMargin = new Thickness(0, 0, _globalToken.Margin, 0); HorizontalItemGutter = 32; HorizontalItemMargin = new Thickness(); @@ -165,6 +174,7 @@ internal class TabControlToken : AbstractControlDesignToken HorizontalItemPadding = new Thickness(0, _globalToken.PaddingSM); HorizontalItemPaddingSM = new Thickness(0, _globalToken.PaddingXS); HorizontalItemPaddingLG = new Thickness(0, _globalToken.Padding); + VerticalItemPadding = new Thickness(_globalToken.PaddingLG, _globalToken.PaddingXS); VerticalItemMargin = new Thickness(0, _globalToken.Margin, 0, 0); @@ -177,6 +187,7 @@ internal class TabControlToken : AbstractControlDesignToken MenuIndicatorPaddingHorizontal = new Thickness(_globalToken.PaddingXS, 0, 0, 0); MenuIndicatorPaddingVertical = new Thickness(0, _globalToken.PaddingXS, 0, 0); + CloseIconMargin = new Thickness(_globalToken.MarginXXS, 0, 0, 0); MenuEdgeThickness = 20; } diff --git a/src/AtomUI.Controls/TabControl/TabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip.cs index 77f30bd..6233dd3 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip.cs @@ -80,6 +80,14 @@ public class TabStrip : BaseTabStrip return tabStripItem; } + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is TabStripItem tabStripItem) { + tabStripItem.Shape = TabSharp.Line; + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); diff --git a/src/AtomUI.Controls/TabControl/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStripItem.cs index 113d96e..2212ba2 100644 --- a/src/AtomUI.Controls/TabControl/TabStripItem.cs +++ b/src/AtomUI.Controls/TabControl/TabStripItem.cs @@ -1,13 +1,17 @@ using AtomUI.Controls.Utils; using AtomUI.Icon; using AtomUI.Media; +using AtomUI.Theme.Data; using AtomUI.Theme.Styling; +using AtomUI.Theme.TokenSystem; using AtomUI.Theme.Utils; using AtomUI.Utils; using Avalonia; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Rendering; @@ -38,6 +42,14 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi public static readonly DirectProperty TabStripPlacementProperty = AvaloniaProperty.RegisterDirect(nameof(TabStripPlacement), o => o.TabStripPlacement); + + internal static readonly StyledProperty CardBorderRadiusProperty = + AvaloniaProperty.Register(nameof(CardBorderRadius)); + + internal static readonly DirectProperty CardBorderRadiusSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(CardBorderRadiusSize), + o => o.CardBorderRadiusSize, + (o, v) => o.CardBorderRadiusSize = v); public SizeType SizeType { @@ -69,6 +81,20 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi get => _tabStripPlacement; internal set => SetAndRaise(TabStripPlacementProperty, ref _tabStripPlacement, value); } + + internal CornerRadius CardBorderRadius + { + get => GetValue(CardBorderRadiusProperty); + set => SetValue(CardBorderRadiusProperty, value); + } + + private CornerRadius _cardBorderRadiusSize; + public CornerRadius CardBorderRadiusSize + { + get => _cardBorderRadiusSize; + internal set => SetAndRaise(CardBorderRadiusSizeProperty, ref _cardBorderRadiusSize, value); + } + #endregion #region 内部属性定义 @@ -84,6 +110,7 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi private StackPanel? _contentLayout; private IControlCustomStyle _customStyle; + private Border? _decorator; public TabStripItem() { @@ -94,7 +121,7 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi { if (Icon is not null) { UIStructureUtils.SetTemplateParent(Icon, this); - Icon.Name = TabStripItemTheme.ItemIconPart; + Icon.Name = BaseTabStripItemTheme.ItemIconPart; if (Icon.ThemeType != IconThemeType.TwoTone) { TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor); TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor); @@ -107,6 +134,27 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi } } + private void SetupCloseIcon() + { + if (CloseIcon is null) { + CloseIcon = new PathIcon + { + Kind = "CloseOutlined" + }; + TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM); + TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM); + } + + CloseIcon.SetValue(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center); + + UIStructureUtils.SetTemplateParent(CloseIcon, this); + if (CloseIcon.ThemeType != IconThemeType.TwoTone) { + TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon); + TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover); + TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled); + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -118,12 +166,20 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi void IControlCustomStyle.HandleTemplateApplied(INameScope scope) { _contentLayout = scope.Find(BaseTabStripItemTheme.ContentLayoutPart); + _decorator = scope.Find(BaseTabStripItemTheme.DecoratorPart); SetupItemIcon(); + SetupCloseIcon(); if (Transitions is null) { var transitions = new Transitions(); transitions.Add(AnimationUtils.CreateTransition(ForegroundProperty)); Transitions = transitions; } + + if (_decorator is not null) { + TokenResourceBinder.CreateTokenBinding(_decorator, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, + new RenderScaleAwareThicknessConfigure(this)); + } + } #endregion @@ -136,15 +192,56 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi if (oldIcon != null) { UIStructureUtils.SetTemplateParent(oldIcon, null); } - SetupItemIcon(); } + } else if (change.Property == TabStripPlacementProperty || + change.Property == SizeTypeProperty) { + if (Shape == TabSharp.Card) { + if (change.Property == SizeTypeProperty) { + HandleSizeTypeChanged(); + } + SetupCardBorderRadius(); + } + } else if (change.Property == ShapeProperty) { + if (Shape == TabSharp.Card) { + SetupCardBorderRadius(); + } + } else if (change.Property == CloseIconProperty) { + var oldIcon = change.GetOldValue(); + if (oldIcon != null) { + UIStructureUtils.SetTemplateParent(oldIcon, null); + } + SetupCloseIcon(); + } + + if (change.Property == ShapeProperty) { + HandleShapeChanged(); } } protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); + HandleShapeChanged(); + HandleSizeTypeChanged(); + if (Shape == TabSharp.Card) { + SetupCardBorderRadius(); + } + } + + private void HandleSizeTypeChanged() + { + if (SizeType == SizeType.Large) { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusLG); + } else if (SizeType == SizeType.Middle) { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadius); + } else { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusSM); + } + } + + private void HandleShapeChanged() + { if (Shape == TabSharp.Line) { TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, TabStripItemTheme.ID); } else { @@ -156,4 +253,11 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi { return true; } + + private void SetupCardBorderRadius() + { + if (TabStripPlacement == Dock.Top) { + CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:0); + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs index 89e207f..6e55066 100644 --- a/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs @@ -1,6 +1,5 @@ using AtomUI.Theme.Styling; using Avalonia.Controls; -using Avalonia.Layout; using Avalonia.Styling; namespace AtomUI.Controls; @@ -21,105 +20,39 @@ internal class TabStripItemTheme : BaseTabStripItemTheme { 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() { var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); { - var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType().Name(DecoratorPart)); + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); 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)); { - var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType().Name(DecoratorPart)); + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); 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)); { - var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType().Name(DecoratorPart)); + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); 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 3cf8255..6b61280 100644 --- a/src/AtomUI.Controls/TabControl/TabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStripTheme.cs @@ -51,27 +51,21 @@ internal class TabStripTheme : BaseTabStripTheme base.BuildStyles(); var commonStyle = new Style(selector => selector.Nesting()); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); + // 设置 items presenter 面板样式 // 分为上、右、下、左 { // 上 var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); - var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType().Name(ItemsPresenterPart).Child().OfType()); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); - + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); indicatorStyle.Add(Border.HeightProperty, GlobalResourceKey.LineWidthBold); indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); 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); } @@ -79,31 +73,18 @@ internal class TabStripTheme : BaseTabStripTheme { // 右 var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC)); - var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType().Name(ItemsPresenterPart).Child().OfType()); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); - rightStyle.Add(itemPresenterPanelStyle); - + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); indicatorStyle.Add(Border.WidthProperty, GlobalResourceKey.LineWidthBold); indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); 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); } { // 下 var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC)); - var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType().Name(ItemsPresenterPart).Child().OfType()); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); - bottomStyle.Add(itemPresenterPanelStyle); var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); indicatorStyle.Add(Border.HeightProperty, GlobalResourceKey.LineWidthBold); @@ -111,21 +92,11 @@ 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); } { // 左 var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC)); - var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType().Name(ItemsPresenterPart).Child().OfType()); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); - leftStyle.Add(itemPresenterPanelStyle); var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); indicatorStyle.Add(Border.WidthProperty, GlobalResourceKey.LineWidthBold); @@ -133,13 +104,6 @@ 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); } From 737039030633fa9ac381fe948f44735381754b10 Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 13:43:13 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20TabStrip=20=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E7=9A=84=E6=96=B9=E5=90=91=E7=9A=84=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCase/TabControlShowCase.axaml | 43 +++++++-- .../ShowCase/TabControlShowCase.axaml.cs | 29 ++++++- .../TokenResourceConst.g.cs | 2 + .../OptionButtonBox/OptionButtonGroup.cs | 29 ++++++- .../TabControl/BaseTabStrip.cs | 11 ++- .../TabControl/BaseTabStripItemTheme.cs | 6 +- .../TabControl/BaseTabStripTheme.cs | 34 +++++--- .../TabControl/TabControlToken.cs | 17 +++- src/AtomUI.Controls/TabControl/TabStrip.cs | 34 +++++++- .../TabControl/TabStripItemTheme.cs | 87 ++++++++++++++----- .../TabControl/TabStripTheme.cs | 28 ++++-- 11 files changed, 252 insertions(+), 68 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 0e02f32..3add5f1 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -88,16 +88,47 @@ + + + + + + + + + + + + + + - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - + + Tab position: + + Top + Bottom + Left + Right + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + Tab Content + + diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs index 82a3960..4a9b184 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs @@ -1,14 +1,37 @@ +using AtomUI.Controls; +using Avalonia; using Avalonia.Controls; -using Button = AtomUI.Controls.Button; -using ToggleSwitch = AtomUI.Controls.ToggleSwitch; namespace AtomUI.Demo.Desktop.ShowCase; public partial class TabControlShowCase : UserControl { + public static readonly StyledProperty PositionTabStripPlacementProperty = + AvaloniaProperty.Register(nameof(PositionTabStripPlacement), Dock.Top); + + public Dock PositionTabStripPlacement + { + get => GetValue(PositionTabStripPlacementProperty); + set => SetValue(PositionTabStripPlacementProperty, value); + } + public TabControlShowCase() { InitializeComponent(); + DataContext = this; + PositionTabStripOptionGroup.OptionCheckedChanged += HandleOptionCheckedChanged; + } + + private void HandleOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + { + if (args.Index == 0) { + PositionTabStripPlacement = Dock.Top; + } else if (args.Index == 1) { + PositionTabStripPlacement = Dock.Bottom; + } else if (args.Index == 2) { + PositionTabStripPlacement = Dock.Left; + } else { + PositionTabStripPlacement = Dock.Right; + } } - } \ 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 f7363cc..afed38b 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 @@ -308,12 +308,14 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey HorizontalItemPadding = new TokenResourceKey("TabControl.HorizontalItemPadding"); public static readonly TokenResourceKey HorizontalItemPaddingLG = new TokenResourceKey("TabControl.HorizontalItemPaddingLG"); public static readonly TokenResourceKey HorizontalItemPaddingSM = new TokenResourceKey("TabControl.HorizontalItemPaddingSM"); + public static readonly TokenResourceKey VerticalItemGutter = new TokenResourceKey("TabControl.VerticalItemGutter"); public static readonly TokenResourceKey VerticalItemPadding = new TokenResourceKey("TabControl.VerticalItemPadding"); public static readonly TokenResourceKey VerticalItemMargin = new TokenResourceKey("TabControl.VerticalItemMargin"); public static readonly TokenResourceKey ItemColor = new TokenResourceKey("TabControl.ItemColor"); 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 CardVerticalGutter = new TokenResourceKey("TabControl.CardVerticalGutter"); public static readonly TokenResourceKey ItemIconMargin = new TokenResourceKey("TabControl.ItemIconMargin"); public static readonly TokenResourceKey MenuIndicatorPaddingHorizontal = new TokenResourceKey("TabControl.MenuIndicatorPaddingHorizontal"); public static readonly TokenResourceKey MenuIndicatorPaddingVertical = new TokenResourceKey("TabControl.MenuIndicatorPaddingVertical"); diff --git a/src/AtomUI.Controls/OptionButtonBox/OptionButtonGroup.cs b/src/AtomUI.Controls/OptionButtonBox/OptionButtonGroup.cs index dd9ac8f..161fe83 100644 --- a/src/AtomUI.Controls/OptionButtonBox/OptionButtonGroup.cs +++ b/src/AtomUI.Controls/OptionButtonBox/OptionButtonGroup.cs @@ -15,9 +15,20 @@ namespace AtomUI.Controls; using ButtonSizeType = SizeType; using OptionButtons = AvaloniaList; -public class OptionButtonGroup : TemplatedControl, - ISizeTypeAware, - IControlCustomStyle +public class OptionCheckedChangedEventArgs : RoutedEventArgs +{ + public OptionCheckedChangedEventArgs(RoutedEvent routedEvent, OptionButton option, int index) + : base(routedEvent) + { + CheckedOption = option; + Index = index; + } + + public OptionButton CheckedOption { get; } + public int Index { get; } +} + +public class OptionButtonGroup : TemplatedControl, ISizeTypeAware, IControlCustomStyle { public static readonly StyledProperty SizeTypeProperty = AvaloniaProperty.Register(nameof(SizeType), ButtonSizeType.Middle); @@ -32,6 +43,17 @@ public class OptionButtonGroup : TemplatedControl, internal static readonly StyledProperty SelectedOptionBorderColorProperty = AvaloniaProperty.Register(nameof(SelectedOptionBorderColor)); + + public static readonly RoutedEvent OptionCheckedChangedEvent = + RoutedEvent.Register( + nameof(OptionCheckedChanged), + RoutingStrategies.Bubble); + + public event EventHandler? OptionCheckedChanged + { + add => AddHandler(OptionCheckedChangedEvent, value); + remove => RemoveHandler(OptionCheckedChangedEvent, value); + } public ButtonSizeType SizeType { @@ -155,6 +177,7 @@ public class OptionButtonGroup : TemplatedControl, if (sender is OptionButton optionButton) { if (optionButton.IsChecked.HasValue && optionButton.IsChecked.Value) { SelectedOption = optionButton; + RaiseEvent(new OptionCheckedChangedEventArgs(OptionCheckedChangedEvent, optionButton, Options.IndexOf(optionButton))); } } } diff --git a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs index a507a77..5847f06 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs @@ -20,9 +20,9 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware public const string RightPC = ":right"; public const string BottomPC = ":bottom"; public const string LeftPC = ":left"; - + private static readonly FuncTemplate DefaultPanel = - new(() => new StackPanel { Orientation = Orientation.Horizontal}); + new(() => new StackPanel()); #region 公共属性定义 public static readonly StyledProperty SizeTypeProperty = @@ -95,8 +95,13 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware { base.OnPropertyChanged(change); if (change.Property == TabStripPlacementProperty) { - RefreshContainers(); UpdatePseudoClasses(); + for (var i = 0; i < ItemCount; ++i) { + var itemContainer = ContainerFromIndex(i); + if (itemContainer is TabStripItem tabStripItem) { + tabStripItem.TabStripPlacement = TabStripPlacement; + } + } } } diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs index 81ccc80..f71fb3e 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs @@ -160,7 +160,8 @@ internal class BaseTabStripItemTheme : BaseControlTheme // 右 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); + iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left); + iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center); rightStyle.Add(iconStyle); Add(rightStyle); } @@ -177,7 +178,8 @@ internal class BaseTabStripItemTheme : BaseControlTheme // 左 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); + iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center); + iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left); leftStyle.Add(iconStyle); Add(leftStyle); } diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs index 0427e22..3c8fa31 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs @@ -44,12 +44,14 @@ internal class BaseTabStripTheme : BaseControlTheme { // 上 var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); - + // 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); + { + var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart)); + itemsPresenterStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); + tabAlignCenterStyle.Add(itemsPresenterStyle); + } topStyle.Add(tabAlignCenterStyle); commonStyle.Add(topStyle); @@ -61,9 +63,11 @@ internal class BaseTabStripTheme : BaseControlTheme // 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); + { + 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); @@ -74,9 +78,11 @@ internal class BaseTabStripTheme : BaseControlTheme // 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); + { + 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); @@ -87,9 +93,11 @@ internal class BaseTabStripTheme : BaseControlTheme // 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); + { + 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); diff --git a/src/AtomUI.Controls/TabControl/TabControlToken.cs b/src/AtomUI.Controls/TabControl/TabControlToken.cs index 2776ca0..e344e81 100644 --- a/src/AtomUI.Controls/TabControl/TabControlToken.cs +++ b/src/AtomUI.Controls/TabControl/TabControlToken.cs @@ -87,6 +87,11 @@ internal class TabControlToken : AbstractControlDesignToken /// public Thickness HorizontalItemPaddingSM { get; set; } + /// + /// 纵向标签页标签间距 + /// + public double VerticalItemGutter { get; set; } + /// /// 纵向标签页标签内间距 /// @@ -117,6 +122,11 @@ internal class TabControlToken : AbstractControlDesignToken /// public double CardGutter { get; set; } + /// + /// 纵向卡片标签间距 + /// + public double CardVerticalGutter { get; set; } + /// /// 标签内容 icon 的外边距 /// @@ -174,15 +184,16 @@ internal class TabControlToken : AbstractControlDesignToken HorizontalItemPadding = new Thickness(0, _globalToken.PaddingSM); HorizontalItemPaddingSM = new Thickness(0, _globalToken.PaddingXS); HorizontalItemPaddingLG = new Thickness(0, _globalToken.Padding); - - VerticalItemPadding = new Thickness(_globalToken.PaddingLG, _globalToken.PaddingXS); - VerticalItemMargin = new Thickness(0, _globalToken.Margin, 0, 0); + + VerticalItemGutter = _globalToken.Margin; + VerticalItemPadding = new Thickness(_globalToken.PaddingXS, _globalToken.PaddingXS); ItemColor = colorToken.ColorNeutralToken.ColorText; ItemSelectedColor = colorToken.ColorPrimaryToken.ColorPrimary; ItemHoverColor = colorToken.ColorPrimaryToken.ColorPrimaryHover; CardGutter = _globalToken.MarginXXS / 2; + CardVerticalGutter = CardGutter; ItemIconMargin = new Thickness(0, 0, _globalToken.MarginSM, 0); MenuIndicatorPaddingHorizontal = new Thickness(_globalToken.PaddingXS, 0, 0, 0); diff --git a/src/AtomUI.Controls/TabControl/TabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip.cs index 6233dd3..b7f09de 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip.cs @@ -1,17 +1,31 @@ using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; +using AtomUI.Utils; using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; +using Avalonia.Layout; using Avalonia.Media.Transformation; namespace AtomUI.Controls; public class TabStrip : BaseTabStrip { + internal static readonly DirectProperty SelectedIndicatorThicknessProperty = + AvaloniaProperty.RegisterDirect(nameof(SelectedIndicatorThickness), + o => o.SelectedIndicatorThickness, + (o, v) => o.SelectedIndicatorThickness = v); + + private double _selectedIndicatorThickness; + internal double SelectedIndicatorThickness + { + get => _selectedIndicatorThickness; + set => SetAndRaise(SelectedIndicatorThicknessProperty, ref _selectedIndicatorThickness, value); + } + private Border? _selectedIndicator; private ItemsPresenter? _itemsPresenter; @@ -19,6 +33,8 @@ public class TabStrip : BaseTabStrip { SelectionChanged += HandleSelectionChanged; LayoutUpdated += HandleLayoutUpdated; + HorizontalAlignment = HorizontalAlignment.Left; + VerticalAlignment = VerticalAlignment.Top; } private void HandleSelectionChanged(object? sender, SelectionChangedEventArgs args) @@ -48,15 +64,23 @@ public class TabStrip : BaseTabStrip 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; + _selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width); + _selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness); builder.AppendTranslate(offset.X + selectedBounds.Left, 0); } else if (TabStripPlacement == Dock.Right) { - _selectedIndicator.Height = tabStripItem.DesiredSize.Height; + _selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height); + _selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness); + builder.AppendTranslate(0, offset.Y + selectedBounds.Y); } else if (TabStripPlacement == Dock.Bottom) { - _selectedIndicator.Width = tabStripItem.DesiredSize.Width; + _selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width); + _selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness); + builder.AppendTranslate(offset.X + selectedBounds.Left, 0); } else { - _selectedIndicator.Height = tabStripItem.DesiredSize.Height; + _selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height); + _selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness); + builder.AppendTranslate(0, offset.Y + selectedBounds.Y); } _selectedIndicator.RenderTransform = builder.Build(); } @@ -93,5 +117,7 @@ public class TabStrip : BaseTabStrip base.OnApplyTemplate(e); _selectedIndicator = e.NameScope.Find(TabStripTheme.SelectedItemIndicatorPart); _itemsPresenter = e.NameScope.Find(TabStripTheme.ItemsPresenterPart); + + TokenResourceBinder.CreateGlobalResourceBinding(this, SelectedIndicatorThicknessProperty, GlobalResourceKey.LineWidthBold); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs index 6e55066..31dcaaa 100644 --- a/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs @@ -19,40 +19,79 @@ internal class TabStripItemTheme : BaseTabStripItemTheme protected override void BuildStyles() { base.BuildStyles(); - - var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType().Name(DecoratorPart)); - decoratorStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin); - Add(decoratorStyle); BuildSizeTypeStyle(); } protected void BuildSizeTypeStyle() { - var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top), + selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom))); + { - var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); - decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingLG); - largeSizeStyle.Add(decoratorStyle); - } + topOrBottomStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin); + + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingLG); + largeSizeStyle.Add(decoratorStyle); + } - Add(largeSizeStyle); + topOrBottomStyle.Add(largeSizeStyle); - var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle)); - { - var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); - decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPadding); - middleSizeStyle.Add(decoratorStyle); - } + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPadding); + middleSizeStyle.Add(decoratorStyle); + } - Add(middleSizeStyle); + topOrBottomStyle.Add(middleSizeStyle); - var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); - { - var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); - decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingSM); - smallSizeType.Add(decoratorStyle); - } + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingSM); + smallSizeType.Add(decoratorStyle); + } - Add(smallSizeType); + topOrBottomStyle.Add(smallSizeType); + + Add(topOrBottomStyle); + } + + var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left), + selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right))); + { + // 貌似没必要分大小,但是先放着吧,万一需要难得再加 + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + largeSizeStyle.Add(decoratorStyle); + } + + leftOrRightStyle.Add(largeSizeStyle); + + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + middleSizeStyle.Add(decoratorStyle); + } + + leftOrRightStyle.Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + smallSizeType.Add(decoratorStyle); + } + + leftOrRightStyle.Add(smallSizeType); + + Add(leftOrRightStyle); + } } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStripTheme.cs index 6b61280..d3041bd 100644 --- a/src/AtomUI.Controls/TabControl/TabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStripTheme.cs @@ -51,17 +51,19 @@ internal class TabStripTheme : BaseTabStripTheme base.BuildStyles(); var commonStyle = new Style(selector => selector.Nesting()); - var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); - // 设置 items presenter 面板样式 // 分为上、右、下、左 { // 上 var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + + topStyle.Add(itemPresenterPanelStyle); + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); - indicatorStyle.Add(Border.HeightProperty, GlobalResourceKey.LineWidthBold); indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom); topStyle.Add(indicatorStyle); @@ -74,8 +76,12 @@ internal class TabStripTheme : BaseTabStripTheme // 右 var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC)); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter); + rightStyle.Add(itemPresenterPanelStyle); + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); - indicatorStyle.Add(Border.WidthProperty, GlobalResourceKey.LineWidthBold); indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); rightStyle.Add(indicatorStyle); @@ -86,8 +92,12 @@ internal class TabStripTheme : BaseTabStripTheme // 下 var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC)); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + bottomStyle.Add(itemPresenterPanelStyle); + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); - indicatorStyle.Add(Border.HeightProperty, GlobalResourceKey.LineWidthBold); indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); bottomStyle.Add(indicatorStyle); @@ -98,8 +108,12 @@ internal class TabStripTheme : BaseTabStripTheme // 左 var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC)); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter); + leftStyle.Add(itemPresenterPanelStyle); + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); - indicatorStyle.Add(Border.WidthProperty, GlobalResourceKey.LineWidthBold); indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right); indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); leftStyle.Add(indicatorStyle); From 42ae225fe348541a890915d1e19e876753e9a17a Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 14:16:36 +0800 Subject: [PATCH 03/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20TabStrip=20=E4=B8=89?= =?UTF-8?q?=E7=A7=8D=E5=B0=BA=E5=AF=B8=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成 TabStrip 三种尺寸的实现,支持默认的系统三种尺寸 --- .../ShowCase/TabControlShowCase.axaml | 225 ++++++++++-------- .../ShowCase/TabControlShowCase.axaml.cs | 25 +- .../TabControl/BaseTabStrip.cs | 6 +- .../TabControl/CardTabStripTheme.cs | 28 ++- .../TabControl/TabStripItem.cs | 2 +- 5 files changed, 177 insertions(+), 109 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 3add5f1..4e8f615 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -8,100 +8,100 @@ xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + Tab 5 + Tab 6 + Tab 7 + Tab 8 + Tab 9 + Tab 10 + Tab 11 + Tab 12 + Tab 13 + Tab 14 + Tab 15 + Tab 16 + Tab 17 + Tab 18 + Tab 19 + Tab 20 + Tab 21 + Tab 22 + Tab 23 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + + + @@ -115,9 +115,9 @@ Right - + - Tab 1 @@ -129,8 +129,39 @@ Tab Content - + + + + + + Tab position: + + Small + Middle + Large + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + + + \ No newline at end of file diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs index 4a9b184..e5cff7b 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs @@ -9,20 +9,30 @@ public partial class TabControlShowCase : UserControl public static readonly StyledProperty PositionTabStripPlacementProperty = AvaloniaProperty.Register(nameof(PositionTabStripPlacement), Dock.Top); + public static readonly StyledProperty SizeTypeTabStripProperty = + AvaloniaProperty.Register(nameof(SizeTypeTabStrip), SizeType.Middle); + public Dock PositionTabStripPlacement { get => GetValue(PositionTabStripPlacementProperty); set => SetValue(PositionTabStripPlacementProperty, value); } + public SizeType SizeTypeTabStrip + { + get => GetValue(SizeTypeTabStripProperty); + set => SetValue(SizeTypeTabStripProperty, value); + } + public TabControlShowCase() { InitializeComponent(); DataContext = this; - PositionTabStripOptionGroup.OptionCheckedChanged += HandleOptionCheckedChanged; + PositionTabStripOptionGroup.OptionCheckedChanged += HandlePlacementOptionCheckedChanged; + SizeTypeTabStripOptionGroup.OptionCheckedChanged += HandleSizeTypeOptionCheckedChanged; } - private void HandleOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + private void HandlePlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) { if (args.Index == 0) { PositionTabStripPlacement = Dock.Top; @@ -34,4 +44,15 @@ public partial class TabControlShowCase : UserControl PositionTabStripPlacement = Dock.Right; } } + + private void HandleSizeTypeOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + { + if (args.Index == 0) { + SizeTypeTabStrip = SizeType.Small; + } else if (args.Index == 1) { + SizeTypeTabStrip = SizeType.Middle; + } else { + SizeTypeTabStrip = SizeType.Large; + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs index 5847f06..fa22c73 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs @@ -1,4 +1,5 @@ -using AtomUI.Theme.Data; +using AtomUI.Data; +using AtomUI.Theme.Data; using AtomUI.Theme.Styling; using AtomUI.Utils; using Avalonia; @@ -26,7 +27,7 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware #region 公共属性定义 public static readonly StyledProperty SizeTypeProperty = - AvaloniaProperty.Register(nameof(SizeType), SizeType.Middle); + AvaloniaProperty.Register(nameof(SizeType), SizeType.Middle); public static readonly StyledProperty TabStripPlacementProperty = AvaloniaProperty.Register(nameof(TabStripPlacement), defaultValue: Dock.Top); @@ -82,6 +83,7 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware base.PrepareContainerForItemOverride(container, item, index); if (container is TabStripItem tabStripItem) { tabStripItem.TabStripPlacement = TabStripPlacement; + BindUtils.RelayBind(this, SizeTypeProperty, tabStripItem, TabStripItem.SizeTypeProperty); } } diff --git a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs index 3b9acf0..9434fbc 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Layout; using Avalonia.Styling; namespace AtomUI.Controls; @@ -37,25 +38,28 @@ internal class CardTabStripTheme : BaseTabStripTheme { base.BuildStyles(); var commonStyle = new Style(selector => selector.Nesting()); - - var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); - - commonStyle.Add(itemPresenterPanelStyle); // 设置 items presenter 面板样式 // 分为上、右、下、左 { // 上 var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); - + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + topStyle.Add(itemPresenterPanelStyle); + commonStyle.Add(topStyle); } { // 右 var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC)); - + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardVerticalGutter); + rightStyle.Add(itemPresenterPanelStyle); commonStyle.Add(rightStyle); } @@ -63,12 +67,22 @@ internal class CardTabStripTheme : BaseTabStripTheme // 下 var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC)); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + bottomStyle.Add(itemPresenterPanelStyle); + commonStyle.Add(bottomStyle); } { // 左 var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC)); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardVerticalGutter); + leftStyle.Add(itemPresenterPanelStyle); + commonStyle.Add(leftStyle); } diff --git a/src/AtomUI.Controls/TabControl/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStripItem.cs index 2212ba2..a226370 100644 --- a/src/AtomUI.Controls/TabControl/TabStripItem.cs +++ b/src/AtomUI.Controls/TabControl/TabStripItem.cs @@ -29,7 +29,7 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi { #region 公共属性定义 public static readonly StyledProperty SizeTypeProperty = - TabStrip.SizeTypeProperty.AddOwner(); + BaseTabStrip.SizeTypeProperty.AddOwner(); public static readonly StyledProperty IconProperty = AvaloniaProperty.Register(nameof(Icon)); From 2060a9dd004d6f78613137072f82f56b3867b4c9 Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 14:50:50 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20CardTabStrip=20?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成 CardTabStrip 位置设置,支持Dock的四个位置 --- .../ShowCase/TabControlShowCase.axaml | 36 +++++++++++++++++-- .../ShowCase/TabControlShowCase.axaml.cs | 23 ++++++++++++ .../TabControl/TabStripItem.cs | 9 ++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 4e8f615..870671f 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -103,8 +103,8 @@ + Title="Position" + Description="Tab's position: left, right, top or bottom. Will auto switch to top in mobile."> Tab position: @@ -133,6 +133,38 @@ + + + + Tab position: + + Top + Bottom + Left + Right + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + Tab Content + + + + + + diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs index e5cff7b..8891a35 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs @@ -9,6 +9,9 @@ public partial class TabControlShowCase : UserControl public static readonly StyledProperty PositionTabStripPlacementProperty = AvaloniaProperty.Register(nameof(PositionTabStripPlacement), Dock.Top); + public static readonly StyledProperty PositionCardTabStripPlacementProperty = + AvaloniaProperty.Register(nameof(PositionCardTabStripPlacement), Dock.Top); + public static readonly StyledProperty SizeTypeTabStripProperty = AvaloniaProperty.Register(nameof(SizeTypeTabStrip), SizeType.Middle); @@ -18,6 +21,12 @@ public partial class TabControlShowCase : UserControl set => SetValue(PositionTabStripPlacementProperty, value); } + public Dock PositionCardTabStripPlacement + { + get => GetValue(PositionCardTabStripPlacementProperty); + set => SetValue(PositionCardTabStripPlacementProperty, value); + } + public SizeType SizeTypeTabStrip { get => GetValue(SizeTypeTabStripProperty); @@ -29,6 +38,7 @@ public partial class TabControlShowCase : UserControl InitializeComponent(); DataContext = this; PositionTabStripOptionGroup.OptionCheckedChanged += HandlePlacementOptionCheckedChanged; + PositionCardTabStripOptionGroup.OptionCheckedChanged += HandleCardPlacementOptionCheckedChanged; SizeTypeTabStripOptionGroup.OptionCheckedChanged += HandleSizeTypeOptionCheckedChanged; } @@ -45,6 +55,19 @@ public partial class TabControlShowCase : UserControl } } + private void HandleCardPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + { + if (args.Index == 0) { + PositionCardTabStripPlacement = Dock.Top; + } else if (args.Index == 1) { + PositionCardTabStripPlacement = Dock.Bottom; + } else if (args.Index == 2) { + PositionCardTabStripPlacement = Dock.Left; + } else { + PositionCardTabStripPlacement = Dock.Right; + } + } + private void HandleSizeTypeOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) { if (args.Index == 0) { diff --git a/src/AtomUI.Controls/TabControl/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStripItem.cs index a226370..4510544 100644 --- a/src/AtomUI.Controls/TabControl/TabStripItem.cs +++ b/src/AtomUI.Controls/TabControl/TabStripItem.cs @@ -194,7 +194,8 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi } SetupItemIcon(); } - } else if (change.Property == TabStripPlacementProperty || + } + if (change.Property == TabStripPlacementProperty || change.Property == SizeTypeProperty) { if (Shape == TabSharp.Card) { if (change.Property == SizeTypeProperty) { @@ -258,6 +259,12 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi { if (TabStripPlacement == Dock.Top) { CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:0); + } else if (TabStripPlacement == Dock.Bottom) { + CardBorderRadius = new CornerRadius(topLeft: 0, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:_cardBorderRadiusSize.BottomRight); + } else if (TabStripPlacement == Dock.Left) { + CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:0); + } else { + CardBorderRadius = new CornerRadius(topLeft: 0, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:_cardBorderRadiusSize.BottomRight); } } } \ No newline at end of file From 85939f5013bc1eaffab7b563796340fa9b9b1df9 Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 17:48:21 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20TabStrip=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成 TabStrip 添加删除功能 --- .../ShowCase/TabControlShowCase.axaml | 397 +++++++++--------- .../ShowCase/TabControlShowCase.axaml.cs | 12 + .../TokenResourceConst.g.cs | 4 +- .../TabControl/BaseTabStripItemTheme.cs | 8 +- .../TabControl/CardTabStrip.cs | 179 +++++++- .../TabControl/CardTabStripItemTheme.cs | 5 +- .../TabControl/CardTabStripTheme.cs | 68 ++- .../TabControl/TabControlToken.cs | 21 +- .../TabControl/TabStripItem.cs | 86 +--- 9 files changed, 490 insertions(+), 290 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 870671f..c9cc350 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -7,193 +7,212 @@ xmlns:atom="https://atomui.net" xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase" mc:Ignorable="d"> - - - - - Tab 1 - Tab 2 - Tab 3 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - Tab 5 - Tab 6 - Tab 7 - Tab 8 - Tab 9 - Tab 10 - Tab 11 - Tab 12 - Tab 13 - Tab 14 - Tab 15 - Tab 16 - Tab 17 - Tab 18 - Tab 19 - Tab 20 - Tab 21 - Tab 22 - Tab 23 - - - - - - - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - - - - - - - - - Tab position: - - Top - Bottom - Left - Right - - - - - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - - - Tab Content - - - - - - - - - - Tab position: - - Top - Bottom - Left - Right - - - - - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - - - Tab Content - - - - - - - - - - Tab position: - - Small - Middle - Large - - - - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - - - - Tab 1 - Tab 2 - Tab 3 - Tab 4 - - - - - - + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + Tab 5 + Tab 6 + Tab 7 + Tab 8 + Tab 9 + Tab 10 + Tab 11 + Tab 12 + Tab 13 + Tab 14 + Tab 15 + Tab 16 + Tab 17 + Tab 18 + Tab 19 + Tab 20 + Tab 21 + Tab 22 + Tab 23 + + + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + + + + + + + Tab position: + + Top + Bottom + Left + Right + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + Tab Content + + + + + + + + + + Tab position: + + Top + Bottom + Left + Right + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + Tab Content + + + + + + + + + + Tab position: + + Small + Middle + Large + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + + + + + + + Tab 1 + Tab 2 + Tab 3 + Tab 4 + + + + + + + \ No newline at end of file diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs index 8891a35..a6a1357 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs @@ -1,6 +1,7 @@ using AtomUI.Controls; using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; namespace AtomUI.Demo.Desktop.ShowCase; @@ -40,6 +41,7 @@ public partial class TabControlShowCase : UserControl PositionTabStripOptionGroup.OptionCheckedChanged += HandlePlacementOptionCheckedChanged; PositionCardTabStripOptionGroup.OptionCheckedChanged += HandleCardPlacementOptionCheckedChanged; SizeTypeTabStripOptionGroup.OptionCheckedChanged += HandleSizeTypeOptionCheckedChanged; + AddTabDemoStrip.AddTabRequest += HandleAddTabRequest; } private void HandlePlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) @@ -78,4 +80,14 @@ public partial class TabControlShowCase : UserControl SizeTypeTabStrip = SizeType.Large; } } + + private void HandleAddTabRequest(object? sender, RoutedEventArgs args) + { + var index = AddTabDemoStrip.ItemCount; + AddTabDemoStrip.Items.Add(new TabStripItem() + { + Content = $"new tab {index}", + IsClosable = true + }); + } } \ 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 afed38b..10122ef 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 @@ -294,7 +294,7 @@ namespace AtomUI.Theme.Styling public static class TabControlResourceKey { public static readonly TokenResourceKey CardBg = new TokenResourceKey("TabControl.CardBg"); - public static readonly TokenResourceKey CardHeight = new TokenResourceKey("TabControl.CardHeight"); + public static readonly TokenResourceKey CardSize = new TokenResourceKey("TabControl.CardSize"); public static readonly TokenResourceKey CardPadding = new TokenResourceKey("TabControl.CardPadding"); public static readonly TokenResourceKey CardPaddingSM = new TokenResourceKey("TabControl.CardPaddingSM"); public static readonly TokenResourceKey CardPaddingLG = new TokenResourceKey("TabControl.CardPaddingLG"); @@ -310,12 +310,10 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey HorizontalItemPaddingSM = new TokenResourceKey("TabControl.HorizontalItemPaddingSM"); public static readonly TokenResourceKey VerticalItemGutter = new TokenResourceKey("TabControl.VerticalItemGutter"); public static readonly TokenResourceKey VerticalItemPadding = new TokenResourceKey("TabControl.VerticalItemPadding"); - public static readonly TokenResourceKey VerticalItemMargin = new TokenResourceKey("TabControl.VerticalItemMargin"); public static readonly TokenResourceKey ItemColor = new TokenResourceKey("TabControl.ItemColor"); 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 CardVerticalGutter = new TokenResourceKey("TabControl.CardVerticalGutter"); public static readonly TokenResourceKey ItemIconMargin = new TokenResourceKey("TabControl.ItemIconMargin"); public static readonly TokenResourceKey MenuIndicatorPaddingHorizontal = new TokenResourceKey("TabControl.MenuIndicatorPaddingHorizontal"); public static readonly TokenResourceKey MenuIndicatorPaddingVertical = new TokenResourceKey("TabControl.MenuIndicatorPaddingVertical"); diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs index f71fb3e..835ca33 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs @@ -18,6 +18,7 @@ internal class BaseTabStripItemTheme : BaseControlTheme public const string ContentLayoutPart = "Part_ContentLayout"; public const string ContentPresenterPart = "PART_ContentPresenter"; public const string ItemIconPart = "PART_ItemIcon"; + public const string ItemCloseButtonPart = "PART_ItemCloseButton"; public BaseTabStripItemTheme(Type targetType) : base(targetType) { } @@ -53,14 +54,17 @@ internal class BaseTabStripItemTheme : BaseControlTheme CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, TabStripItem.ContentProperty); CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, TabStripItem.ContentTemplateProperty); - var iconButton = new IconButton(); + var iconButton = new IconButton() + { + Name = ItemCloseButtonPart + }; + iconButton.RegisterInNameScope(scope); TokenResourceBinder.CreateTokenBinding(iconButton, IconButton.MarginProperty, TabControlResourceKey.CloseIconMargin); CreateTemplateParentBinding(iconButton, IconButton.IconProperty, TabStripItem.CloseIconProperty); CreateTemplateParentBinding(iconButton, IconButton.IsVisibleProperty, TabStripItem.IsClosableProperty); containerLayout.Children.Add(iconButton); - container.Child = containerLayout; } diff --git a/src/AtomUI.Controls/TabControl/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/CardTabStrip.cs index 434d913..761b126 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStrip.cs @@ -1,12 +1,99 @@ -using Avalonia.Controls; +using AtomUI.Data; +using AtomUI.Theme.Data; +using AtomUI.Theme.Styling; +using AtomUI.Utils; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Interactivity; namespace AtomUI.Controls; -public class CardTabStrip : BaseTabStrip +public class CardTabStrip : BaseTabStrip, IControlCustomStyle { + #region 公共属性实现 + public readonly static StyledProperty IsShowAddTabButtonProperty = + AvaloniaProperty.Register(nameof(IsShowAddTabButton), false); + + public readonly static RoutedEvent AddTabRequestEvent = + RoutedEvent.Register( + nameof(AddTabRequest), + RoutingStrategies.Bubble); + + public bool IsShowAddTabButton + { + get => GetValue(IsShowAddTabButtonProperty); + set => SetValue(IsShowAddTabButtonProperty, value); + } + + public event EventHandler? AddTabRequest + { + add => AddHandler(AddTabRequestEvent, value); + remove => RemoveHandler(AddTabRequestEvent, value); + } + + #endregion + + #region 内部属性实现 + + internal static readonly StyledProperty CardBorderRadiusProperty = + AvaloniaProperty.Register(nameof(CardBorderRadius)); + + internal static readonly StyledProperty CardBorderThicknessProperty = + AvaloniaProperty.Register(nameof(CardBorderThickness)); + + internal static readonly DirectProperty CardBorderRadiusSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(CardBorderRadiusSize), + o => o.CardBorderRadiusSize, + (o, v) => o.CardBorderRadiusSize = v); + + internal static readonly DirectProperty CardSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(CardSize), + o => o.CardSize, + (o, v) => o.CardSize = v); + + internal CornerRadius CardBorderRadius + { + get => GetValue(CardBorderRadiusProperty); + set => SetValue(CardBorderRadiusProperty, value); + } + + internal Thickness CardBorderThickness + { + get => GetValue(CardBorderThicknessProperty); + set => SetValue(CardBorderThicknessProperty, value); + } + + private CornerRadius _cardBorderRadiusSize; + internal CornerRadius CardBorderRadiusSize + { + get => _cardBorderRadiusSize; + set => SetAndRaise(CardBorderRadiusSizeProperty, ref _cardBorderRadiusSize, value); + } + + private double _cardSize; + internal double CardSize + { + get => _cardSize; + set => SetAndRaise(CardSizeProperty, ref _cardSize, value); + } + + #endregion + + private IControlCustomStyle _customStyle; + private IconButton? _addTabButton; + private ItemsPresenter? _itemsPresenter; + + public CardTabStrip() + { + _customStyle = this; + } + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { - return new TabStripItem() + return new TabStripItem { Shape = TabSharp.Card }; @@ -17,6 +104,92 @@ public class CardTabStrip : BaseTabStrip base.PrepareContainerForItemOverride(container, item, index); if (container is TabStripItem tabStripItem) { tabStripItem.Shape = TabSharp.Card; + BindUtils.RelayBind(this, CardBorderRadiusProperty, tabStripItem, TabStripItem.CornerRadiusProperty); + BindUtils.RelayBind(this, CardBorderThicknessProperty, tabStripItem, TabStripItem.BorderThicknessProperty); + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + TokenResourceBinder.CreateTokenBinding(this, CardBorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, + new RenderScaleAwareThicknessConfigure(this)); + TokenResourceBinder.CreateTokenBinding(this, CardSizeProperty, TabControlResourceKey.CardSize); + _customStyle.HandleTemplateApplied(e.NameScope); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change.Property == SizeTypeProperty) { + HandleSizeTypeChanged(); + } else if (change.Property == TabStripPlacementProperty) { + HandleTabStripPlacementChanged(); + } + } + + #region IControlCustomStyle 实现 + + void IControlCustomStyle.HandleTemplateApplied(INameScope scope) + { + _addTabButton = scope.Find(CardTabStripTheme.AddTabButtonPart); + _itemsPresenter = scope.Find(CardTabStripTheme.ItemsPresenterPart); + if (_addTabButton is not null) { + _addTabButton.Click += HandleAddButtonClicked; + } + HandleSizeTypeChanged(); + } + #endregion + + private void HandleAddButtonClicked(object? sender, RoutedEventArgs args) + { + RaiseEvent(new RoutedEventArgs(AddTabRequestEvent)); + } + + private void HandleSizeTypeChanged() + { + if (SizeType == SizeType.Large) { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusLG); + } else if (SizeType == SizeType.Middle) { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadius); + } else { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusSM); + } + } + + protected override Size ArrangeOverride(Size finalSize) + { + var size = base.ArrangeOverride(finalSize); + HandleTabStripPlacementChanged(); + return size; + } + + private void HandleTabStripPlacementChanged() + { + if (TabStripPlacement == Dock.Top) { + CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:0); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _cardSize; + _addTabButton.Height = _itemsPresenter.DesiredSize.Height; + } + } else if (TabStripPlacement == Dock.Bottom) { + CardBorderRadius = new CornerRadius(topLeft: 0, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:_cardBorderRadiusSize.BottomRight); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _cardSize; + _addTabButton.Height = _itemsPresenter.DesiredSize.Height; + } + } else if (TabStripPlacement == Dock.Left) { + CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:0); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _itemsPresenter.DesiredSize.Height; + _addTabButton.Height = _cardSize; + } + } else { + CardBorderRadius = new CornerRadius(topLeft: 0, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:_cardBorderRadiusSize.BottomRight); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _itemsPresenter.DesiredSize.Height; + _addTabButton.Height = _cardSize; + } } } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs index cf59844..2d46c04 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs @@ -29,9 +29,8 @@ internal class CardTabStripItemTheme : BaseTabStripItemTheme transitions.Add(AnimationUtils.CreateTransition(Border.BackgroundProperty)); container.Transitions = transitions; } - - - CreateTemplateParentBinding(container, Border.CornerRadiusProperty, TabStripItem.CardBorderRadiusProperty); + CreateTemplateParentBinding(container, Border.BorderThicknessProperty, TabStripItem.BorderThicknessProperty); + CreateTemplateParentBinding(container, Border.CornerRadiusProperty, TabStripItem.CornerRadiusProperty); } protected override void BuildStyles() diff --git a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs index 9434fbc..9a3523c 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs @@ -1,4 +1,6 @@ using AtomUI.Theme.Styling; +using AtomUI.Utils; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; @@ -10,16 +12,58 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class CardTabStripTheme : BaseTabStripTheme { + public const string AddTabButtonPart = "Part_AddTabButton"; + public const string CardTabStripContainerPart = "Part_CardTabStripContainer"; + public CardTabStripTheme() : base(typeof(CardTabStrip)) { } protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container) { + var cardTabStripContainer = new StackPanel() + { + Name = CardTabStripContainerPart + }; + + TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, + TabControlResourceKey.CardGutter); + var tabScrollViewer = new TabScrollViewer(); CreateTemplateParentBinding(tabScrollViewer, TabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabStrip = baseTabStrip; - container.Child = tabScrollViewer; + + var addTabIcon = new PathIcon() + { + Kind = "PlusOutlined" + }; + + TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor); + TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor); + TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled); + + TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSize); + TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSize); + + var addTabButton = new IconButton() + { + Name = AddTabButtonPart, + BorderThickness = new Thickness(1), + Icon = addTabIcon, + Width = 30 + }; + + CreateTemplateParentBinding(addTabButton, IconButton.BorderThicknessProperty, CardTabStrip.CardBorderThicknessProperty); + CreateTemplateParentBinding(addTabButton, IconButton.CornerRadiusProperty, CardTabStrip.CardBorderRadiusProperty); + CreateTemplateParentBinding(addTabButton, IconButton.IsVisibleProperty, CardTabStrip.IsShowAddTabButtonProperty); + + TokenResourceBinder.CreateGlobalResourceBinding(addTabButton, IconButton.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + + addTabButton.RegisterInNameScope(scope); + + cardTabStripContainer.Children.Add(tabScrollViewer); + cardTabStripContainer.Children.Add(addTabButton); + container.Child = cardTabStripContainer; } private ItemsPresenter CreateTabStripContentPanel(INameScope scope) @@ -44,11 +88,15 @@ internal class CardTabStripTheme : BaseTabStripTheme { // 上 var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); + var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart)); + containerStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + topStyle.Add(containerStyle); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); - topStyle.Add(itemPresenterPanelStyle); + topStyle.Add(itemPresenterPanelStyle); commonStyle.Add(topStyle); } @@ -56,9 +104,13 @@ internal class CardTabStripTheme : BaseTabStripTheme // 右 var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC)); + var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart)); + containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + rightStyle.Add(containerStyle); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardVerticalGutter); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); rightStyle.Add(itemPresenterPanelStyle); commonStyle.Add(rightStyle); @@ -67,6 +119,10 @@ internal class CardTabStripTheme : BaseTabStripTheme // 下 var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC)); + var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart)); + containerStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + bottomStyle.Add(containerStyle); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); @@ -78,9 +134,13 @@ internal class CardTabStripTheme : BaseTabStripTheme // 左 var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC)); + var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart)); + containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + leftStyle.Add(containerStyle); + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); - itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardVerticalGutter); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); leftStyle.Add(itemPresenterPanelStyle); commonStyle.Add(leftStyle); diff --git a/src/AtomUI.Controls/TabControl/TabControlToken.cs b/src/AtomUI.Controls/TabControl/TabControlToken.cs index e344e81..3f8997e 100644 --- a/src/AtomUI.Controls/TabControl/TabControlToken.cs +++ b/src/AtomUI.Controls/TabControl/TabControlToken.cs @@ -18,9 +18,9 @@ internal class TabControlToken : AbstractControlDesignToken public Color CardBg { get; set; } /// - /// 卡片标签页高度 + /// 卡片标签页大小 /// - public double CardHeight { get; set; } + public double CardSize { get; set; } /// /// 卡片标签页内间距 @@ -97,11 +97,6 @@ internal class TabControlToken : AbstractControlDesignToken /// public Thickness VerticalItemPadding { get; set; } - /// - /// 纵向标签页标签外间距 - /// - public Thickness VerticalItemMargin { get; set; } - /// /// 标签文本颜色 /// @@ -121,12 +116,7 @@ internal class TabControlToken : AbstractControlDesignToken /// 卡片标签间距 /// public double CardGutter { get; set; } - - /// - /// 纵向卡片标签间距 - /// - public double CardVerticalGutter { get; set; } - + /// /// 标签内容 icon 的外边距 /// @@ -162,9 +152,9 @@ internal class TabControlToken : AbstractControlDesignToken CardBg = _globalToken.ColorFillAlter; - CardHeight = _globalToken.HeightToken.ControlHeightLG; + CardSize = _globalToken.HeightToken.ControlHeightLG; - CardPadding = new Thickness(_globalToken.Padding, (CardHeight - Math.Round(_globalToken.FontToken.FontSize * lineHeight)) / 2 - lineWidth); + CardPadding = new Thickness(_globalToken.Padding, (CardSize - Math.Round(_globalToken.FontToken.FontSize * lineHeight)) / 2 - lineWidth); CardPaddingSM = new Thickness(_globalToken.Padding, _globalToken.PaddingXXS * 1.5); CardPaddingLG = new Thickness(top:_globalToken.PaddingXS, bottom:_globalToken.PaddingXXS * 1.5, @@ -193,7 +183,6 @@ internal class TabControlToken : AbstractControlDesignToken ItemHoverColor = colorToken.ColorPrimaryToken.ColorPrimaryHover; CardGutter = _globalToken.MarginXXS / 2; - CardVerticalGutter = CardGutter; ItemIconMargin = new Thickness(0, 0, _globalToken.MarginSM, 0); MenuIndicatorPaddingHorizontal = new Thickness(_globalToken.PaddingXS, 0, 0, 0); diff --git a/src/AtomUI.Controls/TabControl/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStripItem.cs index 4510544..30c5aa1 100644 --- a/src/AtomUI.Controls/TabControl/TabStripItem.cs +++ b/src/AtomUI.Controls/TabControl/TabStripItem.cs @@ -11,6 +11,7 @@ using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Data; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Rendering; @@ -42,14 +43,6 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi public static readonly DirectProperty TabStripPlacementProperty = AvaloniaProperty.RegisterDirect(nameof(TabStripPlacement), o => o.TabStripPlacement); - - internal static readonly StyledProperty CardBorderRadiusProperty = - AvaloniaProperty.Register(nameof(CardBorderRadius)); - - internal static readonly DirectProperty CardBorderRadiusSizeProperty = - AvaloniaProperty.RegisterDirect(nameof(CardBorderRadiusSize), - o => o.CardBorderRadiusSize, - (o, v) => o.CardBorderRadiusSize = v); public SizeType SizeType { @@ -81,19 +74,6 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi get => _tabStripPlacement; internal set => SetAndRaise(TabStripPlacementProperty, ref _tabStripPlacement, value); } - - internal CornerRadius CardBorderRadius - { - get => GetValue(CardBorderRadiusProperty); - set => SetValue(CardBorderRadiusProperty, value); - } - - private CornerRadius _cardBorderRadiusSize; - public CornerRadius CardBorderRadiusSize - { - get => _cardBorderRadiusSize; - internal set => SetAndRaise(CardBorderRadiusSizeProperty, ref _cardBorderRadiusSize, value); - } #endregion @@ -110,8 +90,8 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi private StackPanel? _contentLayout; private IControlCustomStyle _customStyle; - private Border? _decorator; - + private IconButton? _closeButton; + public TabStripItem() { _customStyle = this; @@ -166,7 +146,8 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi void IControlCustomStyle.HandleTemplateApplied(INameScope scope) { _contentLayout = scope.Find(BaseTabStripItemTheme.ContentLayoutPart); - _decorator = scope.Find(BaseTabStripItemTheme.DecoratorPart); + _closeButton = scope.Find(BaseTabStripItemTheme.ItemCloseButtonPart); + SetupItemIcon(); SetupCloseIcon(); if (Transitions is null) { @@ -175,14 +156,19 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi Transitions = transitions; } - if (_decorator is not null) { - TokenResourceBinder.CreateTokenBinding(_decorator, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, - new RenderScaleAwareThicknessConfigure(this)); + if (_closeButton is not null) { + _closeButton.Click += HandleCloseRequest; } - } #endregion + private void HandleCloseRequest(object? sender, RoutedEventArgs args) + { + if (Parent is BaseTabStrip tabStrip) { + tabStrip.Items.Remove(this); + } + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -195,19 +181,7 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi SetupItemIcon(); } } - if (change.Property == TabStripPlacementProperty || - change.Property == SizeTypeProperty) { - if (Shape == TabSharp.Card) { - if (change.Property == SizeTypeProperty) { - HandleSizeTypeChanged(); - } - SetupCardBorderRadius(); - } - } else if (change.Property == ShapeProperty) { - if (Shape == TabSharp.Card) { - SetupCardBorderRadius(); - } - } else if (change.Property == CloseIconProperty) { + if (change.Property == CloseIconProperty) { var oldIcon = change.GetOldValue(); if (oldIcon != null) { UIStructureUtils.SetTemplateParent(oldIcon, null); @@ -224,23 +198,8 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi { base.OnAttachedToLogicalTree(e); HandleShapeChanged(); - HandleSizeTypeChanged(); - if (Shape == TabSharp.Card) { - SetupCardBorderRadius(); - } } - - private void HandleSizeTypeChanged() - { - if (SizeType == SizeType.Large) { - TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusLG); - } else if (SizeType == SizeType.Middle) { - TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadius); - } else { - TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusSM); - } - } - + private void HandleShapeChanged() { if (Shape == TabSharp.Line) { @@ -254,17 +213,4 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi { return true; } - - private void SetupCardBorderRadius() - { - if (TabStripPlacement == Dock.Top) { - CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:0); - } else if (TabStripPlacement == Dock.Bottom) { - CardBorderRadius = new CornerRadius(topLeft: 0, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:_cardBorderRadiusSize.BottomRight); - } else if (TabStripPlacement == Dock.Left) { - CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:0); - } else { - CardBorderRadius = new CornerRadius(topLeft: 0, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:_cardBorderRadiusSize.BottomRight); - } - } } \ No newline at end of file From 2b9433ad2a6fe3dbc722cef5d595fb4a985d71f1 Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 17:57:23 +0800 Subject: [PATCH 06/28] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20TabStrip=20=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决 TabStrip 对齐问题 --- src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs | 11 +++++++++++ src/AtomUI.Controls/TabControl/TabStrip.cs | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs index 3c8fa31..1480cb3 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs @@ -44,6 +44,8 @@ internal class BaseTabStripTheme : BaseControlTheme { // 上 var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); + topStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + topStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Top); // tabs 是否居中 var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); @@ -61,6 +63,9 @@ internal class BaseTabStripTheme : BaseControlTheme // 右 var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC)); + rightStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Left); + rightStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Stretch); + // tabs 是否居中 var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); { @@ -75,6 +80,8 @@ internal class BaseTabStripTheme : BaseControlTheme { // 下 var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC)); + bottomStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + bottomStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Top); // tabs 是否居中 var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); @@ -91,6 +98,10 @@ internal class BaseTabStripTheme : BaseControlTheme // 左 var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC)); + + leftStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Left); + leftStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Stretch); + // tabs 是否居中 var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); { diff --git a/src/AtomUI.Controls/TabControl/TabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip.cs index b7f09de..8d38a75 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip.cs @@ -33,8 +33,6 @@ public class TabStrip : BaseTabStrip { SelectionChanged += HandleSelectionChanged; LayoutUpdated += HandleLayoutUpdated; - HorizontalAlignment = HorizontalAlignment.Left; - VerticalAlignment = VerticalAlignment.Top; } private void HandleSelectionChanged(object? sender, SelectionChangedEventArgs args) From 64ed57b949d7dc96a17457bd276a942eeafa7ade Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 20:19:48 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E5=AE=8C=E6=88=90CardTabStrip=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=B7=BB=E5=8A=A0=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCase/TabControlShowCase.axaml | 2 +- .../TokenResourceConst.g.cs | 2 ++ .../TabControl/BaseTabStrip.cs | 1 - .../TabControl/CardTabStrip.cs | 25 ++++++++++++- .../TabControl/CardTabStripTheme.cs | 35 +++++++++++++++---- .../TabControl/TabControlToken.cs | 12 +++++++ 6 files changed, 68 insertions(+), 9 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index c9cc350..d40f3e7 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -201,7 +201,7 @@ + Description="Hide default plus icon, and bind event for customized trigger." Margin="0, 0, 30, 0"> Tab 1 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 10122ef..4eb6789 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 @@ -318,6 +318,8 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey MenuIndicatorPaddingHorizontal = new TokenResourceKey("TabControl.MenuIndicatorPaddingHorizontal"); public static readonly TokenResourceKey MenuIndicatorPaddingVertical = new TokenResourceKey("TabControl.MenuIndicatorPaddingVertical"); public static readonly TokenResourceKey MenuEdgeThickness = new TokenResourceKey("TabControl.MenuEdgeThickness"); + public static readonly TokenResourceKey AddTabButtonMarginHorizontal = new TokenResourceKey("TabControl.AddTabButtonMarginHorizontal"); + public static readonly TokenResourceKey AddTabButtonMarginVertical = new TokenResourceKey("TabControl.AddTabButtonMarginVertical"); public static readonly TokenResourceKey CloseIconMargin = new TokenResourceKey("TabControl.CloseIconMargin"); } diff --git a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs index fa22c73..c27d4f9 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabStrip.cs @@ -7,7 +7,6 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; diff --git a/src/AtomUI.Controls/TabControl/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/CardTabStrip.cs index 761b126..543ef24 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStrip.cs @@ -85,6 +85,7 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle private IControlCustomStyle _customStyle; private IconButton? _addTabButton; private ItemsPresenter? _itemsPresenter; + private Grid? _cardTabStripContainer; public CardTabStrip() { @@ -128,12 +129,33 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle } } + private void SetupCardTabStripContainer(Size finalSize) + { + if (_cardTabStripContainer is not null) { + double addButtonOffset = 0; + double markOffset = 0; + if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + addButtonOffset = _addTabButton?.Bounds.Right ?? 0; + markOffset = finalSize.Width; + } else { + addButtonOffset = _addTabButton?.Bounds.Bottom ?? 0; + markOffset = finalSize.Height; + } + if (addButtonOffset > markOffset) { + _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Star; + } else { + _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Auto; + } + } + } + #region IControlCustomStyle 实现 void IControlCustomStyle.HandleTemplateApplied(INameScope scope) { _addTabButton = scope.Find(CardTabStripTheme.AddTabButtonPart); _itemsPresenter = scope.Find(CardTabStripTheme.ItemsPresenterPart); + _cardTabStripContainer = scope.Find(CardTabStripTheme.CardTabStripContainerPart); if (_addTabButton is not null) { _addTabButton.Click += HandleAddButtonClicked; } @@ -156,10 +178,11 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusSM); } } - + protected override Size ArrangeOverride(Size finalSize) { var size = base.ArrangeOverride(finalSize); + SetupCardTabStripContainer(finalSize); HandleTabStripPlacementChanged(); return size; } diff --git a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs index 9a3523c..04a934a 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs @@ -19,15 +19,22 @@ internal class CardTabStripTheme : BaseTabStripTheme protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container) { - var cardTabStripContainer = new StackPanel() + var cardTabStripContainer = new Grid() { - Name = CardTabStripContainerPart + Name = CardTabStripContainerPart, + ColumnDefinitions = + { + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto) + }, }; + cardTabStripContainer.RegisterInNameScope(scope); TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); var tabScrollViewer = new TabScrollViewer(); + Grid.SetColumn(tabScrollViewer, 0); CreateTemplateParentBinding(tabScrollViewer, TabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; @@ -45,14 +52,14 @@ internal class CardTabStripTheme : BaseTabStripTheme TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSize); TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSize); - var addTabButton = new IconButton() + var addTabButton = new IconButton { Name = AddTabButtonPart, BorderThickness = new Thickness(1), - Icon = addTabIcon, - Width = 30 + Icon = addTabIcon }; - + Grid.SetColumn(addTabButton, 1); + CreateTemplateParentBinding(addTabButton, IconButton.BorderThicknessProperty, CardTabStrip.CardBorderThicknessProperty); CreateTemplateParentBinding(addTabButton, IconButton.CornerRadiusProperty, CardTabStrip.CardBorderRadiusProperty); CreateTemplateParentBinding(addTabButton, IconButton.IsVisibleProperty, CardTabStrip.IsShowAddTabButtonProperty); @@ -95,6 +102,10 @@ internal class CardTabStripTheme : BaseTabStripTheme var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal); + topStyle.Add(addTabButtonStyle); topStyle.Add(itemPresenterPanelStyle); commonStyle.Add(topStyle); @@ -113,6 +124,10 @@ internal class CardTabStripTheme : BaseTabStripTheme itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); rightStyle.Add(itemPresenterPanelStyle); + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical); + rightStyle.Add(addTabButtonStyle); + commonStyle.Add(rightStyle); } { @@ -128,6 +143,10 @@ internal class CardTabStripTheme : BaseTabStripTheme itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); bottomStyle.Add(itemPresenterPanelStyle); + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal); + bottomStyle.Add(addTabButtonStyle); + commonStyle.Add(bottomStyle); } { @@ -143,6 +162,10 @@ internal class CardTabStripTheme : BaseTabStripTheme itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); leftStyle.Add(itemPresenterPanelStyle); + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical); + leftStyle.Add(addTabButtonStyle); + commonStyle.Add(leftStyle); } diff --git a/src/AtomUI.Controls/TabControl/TabControlToken.cs b/src/AtomUI.Controls/TabControl/TabControlToken.cs index 3f8997e..a8b7c97 100644 --- a/src/AtomUI.Controls/TabControl/TabControlToken.cs +++ b/src/AtomUI.Controls/TabControl/TabControlToken.cs @@ -137,6 +137,16 @@ internal class TabControlToken : AbstractControlDesignToken /// public double MenuEdgeThickness { get; set; } + /// + /// 水平添加按钮外边距 + /// + public Thickness AddTabButtonMarginHorizontal { get; set; } + + /// + /// 垂直添加按钮外边距 + /// + public Thickness AddTabButtonMarginVertical { get; set; } + /// /// 关闭按钮外边距 /// @@ -183,6 +193,8 @@ internal class TabControlToken : AbstractControlDesignToken ItemHoverColor = colorToken.ColorPrimaryToken.ColorPrimaryHover; CardGutter = _globalToken.MarginXXS / 2; + AddTabButtonMarginHorizontal = new Thickness(_globalToken.MarginXXS, 0, 0, 0); + AddTabButtonMarginVertical = new Thickness(0, _globalToken.MarginXXS, 0, 0); ItemIconMargin = new Thickness(0, 0, _globalToken.MarginSM, 0); MenuIndicatorPaddingHorizontal = new Thickness(_globalToken.PaddingXS, 0, 0, 0); From 0a94172ffbe718a7fd59ea12434064cccd873f6c Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 2 Aug 2024 23:53:38 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20CardTabStrip=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=92=8C=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成 CardTabStrip 新增和删除 --- .../TabControl/TabControlToken.cs | 4 +-- .../TabControl/TabScrollViewer.cs | 23 ++++++++++++++++ .../TabControl/TabStripItem.cs | 14 +++++++++- .../TabControl/TabStripMenuItem.cs | 27 ++++++++++++++----- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/AtomUI.Controls/TabControl/TabControlToken.cs b/src/AtomUI.Controls/TabControl/TabControlToken.cs index a8b7c97..8bace83 100644 --- a/src/AtomUI.Controls/TabControl/TabControlToken.cs +++ b/src/AtomUI.Controls/TabControl/TabControlToken.cs @@ -193,8 +193,8 @@ internal class TabControlToken : AbstractControlDesignToken ItemHoverColor = colorToken.ColorPrimaryToken.ColorPrimaryHover; CardGutter = _globalToken.MarginXXS / 2; - AddTabButtonMarginHorizontal = new Thickness(_globalToken.MarginXXS, 0, 0, 0); - AddTabButtonMarginVertical = new Thickness(0, _globalToken.MarginXXS, 0, 0); + AddTabButtonMarginHorizontal = new Thickness(CardGutter, 0, 0, 0); + AddTabButtonMarginVertical = new Thickness(0, CardGutter, 0, 0); ItemIconMargin = new Thickness(0, 0, _globalToken.MarginSM, 0); MenuIndicatorPaddingHorizontal = new Thickness(_globalToken.PaddingXS, 0, 0, 0); diff --git a/src/AtomUI.Controls/TabControl/TabScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabScrollViewer.cs index 4cf681b..0921586 100644 --- a/src/AtomUI.Controls/TabControl/TabScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabScrollViewer.cs @@ -217,6 +217,7 @@ public class TabScrollViewer : ScrollViewer IsClosable = tabStripItem.IsClosable }; menuItem.Click += HandleMenuItemClicked; + menuItem.CloseTab += HandleCloseTabRequest; _menuFlyout.Items.Add(menuItem); } } @@ -245,6 +246,28 @@ public class TabScrollViewer : ScrollViewer } } + private void HandleCloseTabRequest(object? sender, RoutedEventArgs args) + { + if (sender is TabStripMenuItem tabStripMenuItem) { + if (TabStrip is not null) { + if (TabStrip.SelectedItem is TabStripItem selectedItem) { + if (selectedItem == tabStripMenuItem.TabStripItem) { + var selectedIndex = TabStrip.SelectedIndex; + object? newSelectedItem = null; + if (selectedIndex != 0) { + newSelectedItem = TabStrip.Items[--selectedIndex]; + } + TabStrip.Items.Remove(tabStripMenuItem.TabStripItem); + TabStrip.SelectedItem = newSelectedItem; + } else { + TabStrip.Items.Remove(tabStripMenuItem.TabStripItem); + } + } + } + + } + } + protected override Size MeasureOverride(Size availableSize) { var size = base.MeasureOverride(availableSize); diff --git a/src/AtomUI.Controls/TabControl/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStripItem.cs index 30c5aa1..9f1b180 100644 --- a/src/AtomUI.Controls/TabControl/TabStripItem.cs +++ b/src/AtomUI.Controls/TabControl/TabStripItem.cs @@ -165,7 +165,19 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi private void HandleCloseRequest(object? sender, RoutedEventArgs args) { if (Parent is BaseTabStrip tabStrip) { - tabStrip.Items.Remove(this); + if (tabStrip.SelectedItem is TabStripItem selectedItem) { + if (selectedItem == this) { + var selectedIndex = tabStrip.SelectedIndex; + object? newSelectedItem = null; + if (selectedIndex != 0) { + newSelectedItem = tabStrip.Items[--selectedIndex]; + } + tabStrip.Items.Remove(this); + tabStrip.SelectedItem = newSelectedItem; + } else { + tabStrip.Items.Remove(this); + } + } } } diff --git a/src/AtomUI.Controls/TabControl/TabStripMenuItem.cs b/src/AtomUI.Controls/TabControl/TabStripMenuItem.cs index 5c79dd0..d395ff5 100644 --- a/src/AtomUI.Controls/TabControl/TabStripMenuItem.cs +++ b/src/AtomUI.Controls/TabControl/TabStripMenuItem.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Threading; namespace AtomUI.Controls; @@ -15,8 +16,8 @@ internal class TabStripMenuItem : MenuItem o => o.IsClosable, (o, v) => o.IsClosable = v); - public static readonly RoutedEvent CloseTabEvent = - RoutedEvent.Register(nameof(CloseTab), RoutingStrategies.Bubble); + public static readonly RoutedEvent CloseTabEvent = + RoutedEvent.Register(nameof(CloseTab), RoutingStrategies.Bubble); private bool _isClosable = false; public bool IsClosable @@ -27,7 +28,7 @@ internal class TabStripMenuItem : MenuItem public TabStripItem? TabStripItem { get; set; } - public event EventHandler? CloseTab + public event EventHandler? CloseTab { add => AddHandler(CloseTabEvent, value); remove => RemoveHandler(CloseTabEvent, value); @@ -44,13 +45,25 @@ internal class TabStripMenuItem : MenuItem if (_iconButton is not null) { _iconButton.Click += (sender, args) => { - var menu = this.FindLogicalAncestorOfType(); - if (menu is not null) { - menu.Close(); - var eventArgs = new RoutedEventArgs(CloseTabEvent); + if (Parent is MenuBase menu) { + var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabStripItem!); RaiseEvent(eventArgs); + Dispatcher.UIThread.Post(() => + { + menu.Close(); + }); } }; } } +} + +internal class CloseTabRequestEventArgs : RoutedEventArgs +{ + public CloseTabRequestEventArgs(RoutedEvent routedEvent, TabStripItem stripItem) + : base(routedEvent) + { + TabStripItem = stripItem; + } + public TabStripItem TabStripItem { get; } } \ No newline at end of file From e35c7fc0d27397ee79889acd84c990c7b4713c26 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 00:46:37 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20CardStrip=20?= =?UTF-8?q?=E5=9E=82=E7=9B=B4=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCase/TabControlShowCase.axaml | 3 +- .../TabControl/CardTabStrip.cs | 70 +++++++++++++++-- .../TabControl/CardTabStripItemTheme.cs | 75 ++++++++++++++----- .../TabControl/CardTabStripTheme.cs | 15 ++-- 4 files changed, 126 insertions(+), 37 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index d40f3e7..b6cf5d8 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -154,7 +154,8 @@ + TabStripPlacement="{Binding PositionCardTabStripPlacement}" + IsShowAddTabButton="True"> Tab 1 Tab 2 Tab 3 diff --git a/src/AtomUI.Controls/TabControl/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/CardTabStrip.cs index 543ef24..dfc0da0 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStrip.cs @@ -86,6 +86,7 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle private IconButton? _addTabButton; private ItemsPresenter? _itemsPresenter; private Grid? _cardTabStripContainer; + private TabScrollViewer? _tabScrollViewer; public CardTabStrip() { @@ -125,6 +126,7 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle if (change.Property == SizeTypeProperty) { HandleSizeTypeChanged(); } else if (change.Property == TabStripPlacementProperty) { + SetupCardTabStripContainer(); HandleTabStripPlacementChanged(); } } @@ -137,15 +139,21 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { addButtonOffset = _addTabButton?.Bounds.Right ?? 0; markOffset = finalSize.Width; + if (addButtonOffset > markOffset) { + _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Star; + } else { + _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Auto; + } } else { addButtonOffset = _addTabButton?.Bounds.Bottom ?? 0; markOffset = finalSize.Height; + if (addButtonOffset > markOffset) { + _cardTabStripContainer.RowDefinitions[0].Height = GridLength.Star; + } else { + _cardTabStripContainer.RowDefinitions[0].Height = GridLength.Auto; + } } - if (addButtonOffset > markOffset) { - _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Star; - } else { - _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Auto; - } + } } @@ -156,10 +164,12 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle _addTabButton = scope.Find(CardTabStripTheme.AddTabButtonPart); _itemsPresenter = scope.Find(CardTabStripTheme.ItemsPresenterPart); _cardTabStripContainer = scope.Find(CardTabStripTheme.CardTabStripContainerPart); + _tabScrollViewer = scope.Find(CardTabStripTheme.CardTabStripScrollViewerPart); if (_addTabButton is not null) { _addTabButton.Click += HandleAddButtonClicked; } HandleSizeTypeChanged(); + SetupCardTabStripContainer(); } #endregion @@ -186,6 +196,52 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle HandleTabStripPlacementChanged(); return size; } + + private void SetupCardTabStripContainer() + { + if (TabStripPlacement == Dock.Top || + TabStripPlacement == Dock.Bottom) { + if (_cardTabStripContainer is not null) { + _cardTabStripContainer.Children.Clear(); + _cardTabStripContainer.RowDefinitions.Clear(); + _cardTabStripContainer.ColumnDefinitions = new ColumnDefinitions() + { + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + }; + } + + if (_tabScrollViewer is not null) { + Grid.SetColumn(_tabScrollViewer, 0); + } + + if (_addTabButton is not null) { + Grid.SetColumn(_addTabButton, 1); + } + + _cardTabStripContainer!.Children.Add(_tabScrollViewer!); + _cardTabStripContainer.Children.Add(_addTabButton!); + } else { + if (_cardTabStripContainer is not null) { + _cardTabStripContainer.Children.Clear(); + _cardTabStripContainer.ColumnDefinitions.Clear(); + _cardTabStripContainer.RowDefinitions = new RowDefinitions() + { + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }; + } + if (_tabScrollViewer is not null) { + Grid.SetRow(_tabScrollViewer, 0); + } + + if (_addTabButton is not null) { + Grid.SetRow(_addTabButton, 1); + } + _cardTabStripContainer!.Children.Add(_tabScrollViewer!); + _cardTabStripContainer.Children.Add(_addTabButton!); + } + } private void HandleTabStripPlacementChanged() { @@ -204,13 +260,13 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle } else if (TabStripPlacement == Dock.Left) { CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:0); if (_addTabButton is not null && _itemsPresenter is not null) { - _addTabButton.Width = _itemsPresenter.DesiredSize.Height; + _addTabButton.Width = _itemsPresenter.DesiredSize.Width; _addTabButton.Height = _cardSize; } } else { CardBorderRadius = new CornerRadius(topLeft: 0, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:_cardBorderRadiusSize.BottomRight); if (_addTabButton is not null && _itemsPresenter is not null) { - _addTabButton.Width = _itemsPresenter.DesiredSize.Height; + _addTabButton.Width = _itemsPresenter.DesiredSize.Width; _addTabButton.Height = _cardSize; } } diff --git a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs index 2d46c04..e75c496 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs @@ -64,30 +64,65 @@ internal class CardTabStripItemTheme : BaseTabStripItemTheme protected void BuildSizeTypeStyle() { - var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top), + selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom))); { - var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); - decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingLG); - largeSizeStyle.Add(decoratorStyle); - } - Add(largeSizeStyle); + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingLG); + largeSizeStyle.Add(decoratorStyle); + } + topOrBottomStyle.Add(largeSizeStyle); - var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle)); - { - var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); - decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPadding); - middleSizeStyle.Add(decoratorStyle); - } - Add(middleSizeStyle); + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPadding); + middleSizeStyle.Add(decoratorStyle); + } + topOrBottomStyle.Add(middleSizeStyle); - var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); - { - var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); - decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingSM); - smallSizeType.Add(decoratorStyle); - } + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingSM); + smallSizeType.Add(decoratorStyle); + } - Add(smallSizeType); + topOrBottomStyle.Add(smallSizeType); + } + Add(topOrBottomStyle); + + var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left), + selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right))); + { + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + largeSizeStyle.Add(decoratorStyle); + } + leftOrRightStyle.Add(largeSizeStyle); + + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + middleSizeStyle.Add(decoratorStyle); + } + leftOrRightStyle.Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + smallSizeType.Add(decoratorStyle); + } + + leftOrRightStyle.Add(smallSizeType); + } + Add(leftOrRightStyle); } private void BuildPlacementStyle() diff --git a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs index 04a934a..7e5ddc6 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs @@ -14,6 +14,7 @@ internal class CardTabStripTheme : BaseTabStripTheme { public const string AddTabButtonPart = "Part_AddTabButton"; public const string CardTabStripContainerPart = "Part_CardTabStripContainer"; + public const string CardTabStripScrollViewerPart = "Part_CardTabStripScrollViewer"; public CardTabStripTheme() : base(typeof(CardTabStrip)) { } @@ -22,19 +23,17 @@ internal class CardTabStripTheme : BaseTabStripTheme var cardTabStripContainer = new Grid() { Name = CardTabStripContainerPart, - ColumnDefinitions = - { - new ColumnDefinition(GridLength.Auto), - new ColumnDefinition(GridLength.Auto) - }, }; cardTabStripContainer.RegisterInNameScope(scope); TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); - var tabScrollViewer = new TabScrollViewer(); - Grid.SetColumn(tabScrollViewer, 0); + var tabScrollViewer = new TabScrollViewer() + { + Name = CardTabStripScrollViewerPart + }; + tabScrollViewer.RegisterInNameScope(scope); CreateTemplateParentBinding(tabScrollViewer, TabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; @@ -58,7 +57,6 @@ internal class CardTabStripTheme : BaseTabStripTheme BorderThickness = new Thickness(1), Icon = addTabIcon }; - Grid.SetColumn(addTabButton, 1); CreateTemplateParentBinding(addTabButton, IconButton.BorderThicknessProperty, CardTabStrip.CardBorderThicknessProperty); CreateTemplateParentBinding(addTabButton, IconButton.CornerRadiusProperty, CardTabStrip.CardBorderRadiusProperty); @@ -96,7 +94,6 @@ internal class CardTabStripTheme : BaseTabStripTheme // 上 var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC)); var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart)); - containerStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); topStyle.Add(containerStyle); var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); From f962cc3bb265a8e41ee818b20c01ac99263a5954 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 10:21:00 +0800 Subject: [PATCH 10/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20CardStrip=20?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加 CardStrip 禁用样式 --- .../ShowCase/TabControlShowCase.axaml | 6 ++++++ .../TabControl/CardTabStripItemTheme.cs | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index b6cf5d8..cad3905 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -26,6 +26,12 @@ Title="Disabled" Description="Disabled a tab."> + + Tab 1 + Tab 2 + Tab 3 + + Tab 1 Tab 2 diff --git a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs index e75c496..9c28577 100644 --- a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs @@ -60,6 +60,7 @@ internal class CardTabStripItemTheme : BaseTabStripItemTheme BuildSizeTypeStyle(); BuildPlacementStyle(); + BuildDisabledStyle(); } protected void BuildSizeTypeStyle() @@ -168,4 +169,13 @@ internal class CardTabStripItemTheme : BaseTabStripItemTheme Add(leftStyle); } } + + private void BuildDisabledStyle() + { + var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled)); + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainerDisabled); + disabledStyle.Add(decoratorStyle); + Add(disabledStyle); + } } \ No newline at end of file From d0fc79ed82b80983673981fdbf52eed437999e8f Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 10:31:17 +0800 Subject: [PATCH 11/28] =?UTF-8?q?=E9=87=8D=E6=9E=84=20TabStrip=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为了重构方便,把 TabStrip 放入自己的专属文件夹 --- ...tripMenuItem.cs => OverflowTabMenuItem.cs} | 9 ++++----- ...emTheme.cs => OverflowTabMenuItemTheme.cs} | 20 +++++++++---------- .../TabControl/TabScrollViewer.cs | 6 +++--- .../TabControl/{ => TabStrip}/BaseTabStrip.cs | 0 .../{ => TabStrip}/BaseTabStripItemTheme.cs | 0 .../{ => TabStrip}/BaseTabStripTheme.cs | 0 .../TabControl/{ => TabStrip}/CardTabStrip.cs | 0 .../{ => TabStrip}/CardTabStripItemTheme.cs | 0 .../{ => TabStrip}/CardTabStripTheme.cs | 0 .../TabControl/{ => TabStrip}/TabStrip.cs | 0 .../TabControl/{ => TabStrip}/TabStripItem.cs | 0 .../{ => TabStrip}/TabStripItemTheme.cs | 0 .../TabStripScrollContentPresenter.cs | 0 .../{ => TabStrip}/TabStripTheme.cs | 0 14 files changed, 17 insertions(+), 18 deletions(-) rename src/AtomUI.Controls/TabControl/{TabStripMenuItem.cs => OverflowTabMenuItem.cs} (84%) rename src/AtomUI.Controls/TabControl/{TabStripMenuItemTheme.cs => OverflowTabMenuItemTheme.cs} (87%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/BaseTabStrip.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/BaseTabStripItemTheme.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/BaseTabStripTheme.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/CardTabStrip.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/CardTabStripItemTheme.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/CardTabStripTheme.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/TabStrip.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/TabStripItem.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/TabStripItemTheme.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/TabStripScrollContentPresenter.cs (100%) rename src/AtomUI.Controls/TabControl/{ => TabStrip}/TabStripTheme.cs (100%) diff --git a/src/AtomUI.Controls/TabControl/TabStripMenuItem.cs b/src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs similarity index 84% rename from src/AtomUI.Controls/TabControl/TabStripMenuItem.cs rename to src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs index d395ff5..0754eed 100644 --- a/src/AtomUI.Controls/TabControl/TabStripMenuItem.cs +++ b/src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs @@ -2,17 +2,16 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; -using Avalonia.LogicalTree; using Avalonia.Threading; namespace AtomUI.Controls; -internal class TabStripMenuItem : MenuItem +internal class OverflowTabMenuItem : MenuItem { #region 公共属性 - public static readonly DirectProperty IsClosableProperty = - AvaloniaProperty.RegisterDirect(nameof(IsClosable), + public static readonly DirectProperty IsClosableProperty = + AvaloniaProperty.RegisterDirect(nameof(IsClosable), o => o.IsClosable, (o, v) => o.IsClosable = v); @@ -41,7 +40,7 @@ internal class TabStripMenuItem : MenuItem protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _iconButton = e.NameScope.Find(TabStripMenuItemTheme.ItemCloseButtonPart); + _iconButton = e.NameScope.Find(OverflowTabMenuItemTheme.ItemCloseButtonPart); if (_iconButton is not null) { _iconButton.Click += (sender, args) => { diff --git a/src/AtomUI.Controls/TabControl/TabStripMenuItemTheme.cs b/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs similarity index 87% rename from src/AtomUI.Controls/TabControl/TabStripMenuItemTheme.cs rename to src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs index 3b3b7bf..e49bedc 100644 --- a/src/AtomUI.Controls/TabControl/TabStripMenuItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs @@ -13,21 +13,21 @@ using Avalonia.Styling; namespace AtomUI.Controls; [ControlThemeProvider] -internal class TabStripMenuItemTheme : BaseControlTheme +internal class OverflowTabMenuItemTheme : BaseControlTheme { public const string ItemDecoratorPart = "Part_ItemDecorator"; public const string MainContainerPart = "Part_MainContainer"; public const string ItemTextPresenterPart = "Part_ItemTextPresenter"; public const string ItemCloseButtonPart = "Part_ItemCloseIcon"; - public TabStripMenuItemTheme() - : base(typeof(TabStripMenuItem)) + public OverflowTabMenuItemTheme() + : base(typeof(OverflowTabMenuItem)) { } protected override IControlTemplate BuildControlTemplate() { - return new FuncControlTemplate((item, scope) => + return new FuncControlTemplate((item, scope) => { var container = new Border() { @@ -61,8 +61,8 @@ internal class TabStripMenuItemTheme : BaseControlTheme Grid.SetColumn(itemTextPresenter, 0); TokenResourceBinder.CreateTokenBinding(itemTextPresenter, ContentPresenter.MarginProperty, MenuResourceKey.ItemMargin); - CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, TabStripMenuItem.HeaderProperty); - CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, TabStripMenuItem.HeaderTemplateProperty); + CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, OverflowTabMenuItem.HeaderProperty); + CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, OverflowTabMenuItem.HeaderTemplateProperty); itemTextPresenter.RegisterInNameScope(scope); @@ -82,7 +82,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme }; - CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, TabStripMenuItem.IsClosableProperty); + CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, OverflowTabMenuItem.IsClosableProperty); TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon); TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover); @@ -110,7 +110,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme private void BuildCommonStyle(Style commonStyle) { - commonStyle.Add(TabStripMenuItem.ForegroundProperty, MenuResourceKey.ItemColor); + commonStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemColor); { var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); borderStyle.Add(Border.MinHeightProperty, MenuResourceKey.ItemHeight); @@ -122,7 +122,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme // Hover 状态 var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); - hoverStyle.Add(TabStripMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor); + hoverStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor); { var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); borderStyle.Add(Border.BackgroundProperty, MenuResourceKey.ItemHoverBg); @@ -134,7 +134,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme private void BuildDisabledStyle() { var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled)); - disabledStyle.Add(TabStripMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor); + disabledStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor); Add(disabledStyle); } diff --git a/src/AtomUI.Controls/TabControl/TabScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabScrollViewer.cs index 0921586..a431b45 100644 --- a/src/AtomUI.Controls/TabControl/TabScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabScrollViewer.cs @@ -210,7 +210,7 @@ public class TabScrollViewer : ScrollViewer var right = Math.Floor(itemBounds.Right - Offset.X); if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { if (left < 0 || right > Viewport.Width) { - var menuItem = new TabStripMenuItem() + var menuItem = new OverflowTabMenuItem() { Header = tabStripItem.Content, TabStripItem = tabStripItem, @@ -235,7 +235,7 @@ public class TabScrollViewer : ScrollViewer if (TabStrip is not null) { Dispatcher.UIThread.Post(sender => { - if (sender is TabStripMenuItem tabStripMenuItem) { + if (sender is OverflowTabMenuItem tabStripMenuItem) { var tabStripItem = tabStripMenuItem.TabStripItem; if (tabStripItem is not null) { tabStripItem.BringIntoView(); @@ -248,7 +248,7 @@ public class TabScrollViewer : ScrollViewer private void HandleCloseTabRequest(object? sender, RoutedEventArgs args) { - if (sender is TabStripMenuItem tabStripMenuItem) { + if (sender is OverflowTabMenuItem tabStripMenuItem) { if (TabStrip is not null) { if (TabStrip.SelectedItem is TabStripItem selectedItem) { if (selectedItem == tabStripMenuItem.TabStripItem) { diff --git a/src/AtomUI.Controls/TabControl/BaseTabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStrip.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/BaseTabStrip.cs rename to src/AtomUI.Controls/TabControl/TabStrip/BaseTabStrip.cs diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/BaseTabStripItemTheme.cs rename to src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs diff --git a/src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/BaseTabStripTheme.cs rename to src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs diff --git a/src/AtomUI.Controls/TabControl/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/CardTabStrip.cs rename to src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs diff --git a/src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripItemTheme.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/CardTabStripItemTheme.cs rename to src/AtomUI.Controls/TabControl/TabStrip/CardTabStripItemTheme.cs diff --git a/src/AtomUI.Controls/TabControl/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/CardTabStripTheme.cs rename to src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs diff --git a/src/AtomUI.Controls/TabControl/TabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/TabStrip.cs rename to src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs diff --git a/src/AtomUI.Controls/TabControl/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripItem.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/TabStripItem.cs rename to src/AtomUI.Controls/TabControl/TabStrip/TabStripItem.cs diff --git a/src/AtomUI.Controls/TabControl/TabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripItemTheme.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/TabStripItemTheme.cs rename to src/AtomUI.Controls/TabControl/TabStrip/TabStripItemTheme.cs diff --git a/src/AtomUI.Controls/TabControl/TabStripScrollContentPresenter.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollContentPresenter.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/TabStripScrollContentPresenter.cs rename to src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollContentPresenter.cs diff --git a/src/AtomUI.Controls/TabControl/TabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs similarity index 100% rename from src/AtomUI.Controls/TabControl/TabStripTheme.cs rename to src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs From d1784e6e9e56ff68f2563b51eb6fcca15eb3c6a8 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 12:57:16 +0800 Subject: [PATCH 12/28] =?UTF-8?q?=E9=87=8D=E6=9E=84=20TabStripScrollViewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构 TabStripScrollViewer 方便同时支持 TabStrip 和 TabControl --- ...ScrollViewer.cs => BaseTabScrollViewer.cs} | 133 +++--------------- .../TabControl/TabControlScrollViewer.cs | 6 + ...senter.cs => TabScrollContentPresenter.cs} | 4 +- .../TabStrip/TabStripScrollViewer.cs | 104 ++++++++++++++ 4 files changed, 135 insertions(+), 112 deletions(-) rename src/AtomUI.Controls/TabControl/{TabScrollViewer.cs => BaseTabScrollViewer.cs} (62%) create mode 100644 src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs rename src/AtomUI.Controls/TabControl/{TabStrip/TabStripScrollContentPresenter.cs => TabScrollContentPresenter.cs} (93%) create mode 100644 src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs diff --git a/src/AtomUI.Controls/TabControl/TabScrollViewer.cs b/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs similarity index 62% rename from src/AtomUI.Controls/TabControl/TabScrollViewer.cs rename to src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs index a431b45..c5bcc0f 100644 --- a/src/AtomUI.Controls/TabControl/TabScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs @@ -15,28 +15,28 @@ using GradientStop = Avalonia.Media.GradientStop; namespace AtomUI.Controls; -[TemplatePart(TabScrollViewerTheme.ScrollStartEdgeIndicatorPart, typeof(Control))] -[TemplatePart(TabScrollViewerTheme.ScrollEndEdgeIndicatorPart, typeof(Control))] -[TemplatePart(TabScrollViewerTheme.ScrollMenuIndicatorPart, typeof(IconButton))] -[TemplatePart(TabScrollViewerTheme.ScrollViewContentPart, typeof(ScrollContentPresenter))] -public class TabScrollViewer : ScrollViewer +[TemplatePart(BaseTabScrollViewerTheme.ScrollStartEdgeIndicatorPart, typeof(Control))] +[TemplatePart(BaseTabScrollViewerTheme.ScrollEndEdgeIndicatorPart, typeof(Control))] +[TemplatePart(BaseTabScrollViewerTheme.ScrollMenuIndicatorPart, typeof(IconButton))] +[TemplatePart(BaseTabScrollViewerTheme.ScrollViewContentPart, typeof(ScrollContentPresenter))] +internal abstract class BaseTabScrollViewer : ScrollViewer { private const int EdgeIndicatorZIndex = 1000; #region 内部属性定义 - internal static readonly DirectProperty TabStripPlacementProperty = - AvaloniaProperty.RegisterDirect(nameof(TabStripPlacement), + internal static readonly DirectProperty TabStripPlacementProperty = + AvaloniaProperty.RegisterDirect(nameof(TabStripPlacement), o => o.TabStripPlacement, (o, v) => o.TabStripPlacement = v); - internal static readonly DirectProperty EdgeShadowStartColorProperty = - AvaloniaProperty.RegisterDirect(nameof(EdgeShadowStartColor), + internal static readonly DirectProperty EdgeShadowStartColorProperty = + AvaloniaProperty.RegisterDirect(nameof(EdgeShadowStartColor), o => o.EdgeShadowStartColor, (o, v) => o.EdgeShadowStartColor = v); - internal static readonly DirectProperty MenuEdgeThicknessProperty = - AvaloniaProperty.RegisterDirect(nameof(MenuEdgeThickness), + internal static readonly DirectProperty MenuEdgeThicknessProperty = + AvaloniaProperty.RegisterDirect(nameof(MenuEdgeThickness), o => o.MenuEdgeThickness, (o, v) => o.MenuEdgeThickness = v); @@ -64,21 +64,19 @@ public class TabScrollViewer : ScrollViewer set => SetAndRaise(MenuEdgeThicknessProperty, ref _menuEdgeThickness, value); } - internal BaseTabStrip? TabStrip { get; set; } - #endregion - private IconButton? _menuIndicator; - private Border? _startEdgeIndicator; - private Border? _endEdgeIndicator; - private MenuFlyout? _menuFlyout; + private protected IconButton? _menuIndicator; + private protected Border? _startEdgeIndicator; + private protected Border? _endEdgeIndicator; + private protected MenuFlyout? _menuFlyout; - static TabScrollViewer() + static BaseTabScrollViewer() { - AffectsMeasure(TabStripPlacementProperty); + AffectsMeasure(TabStripPlacementProperty); } - public TabScrollViewer() + public BaseTabScrollViewer() { HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; } @@ -87,7 +85,7 @@ public class TabScrollViewer : ScrollViewer { base.OnPropertyChanged(change); if (change.Property == TabStripPlacementProperty) { - if (Presenter is TabStripScrollContentPresenter tabStripScrollContentPresenter) { + if (Presenter is TabScrollContentPresenter tabStripScrollContentPresenter) { tabStripScrollContentPresenter.TabStripPlacement = TabStripPlacement; } } else if (change.Property == VerticalScrollBarVisibilityProperty || @@ -163,7 +161,7 @@ public class TabScrollViewer : ScrollViewer protected override bool RegisterContentPresenter(ContentPresenter presenter) { - if (presenter is TabStripScrollContentPresenter tabStripScrollContentPresenter) { + if (presenter is TabScrollContentPresenter tabStripScrollContentPresenter) { tabStripScrollContentPresenter.TabStripPlacement = TabStripPlacement; } @@ -173,101 +171,16 @@ public class TabScrollViewer : ScrollViewer protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _menuIndicator = e.NameScope.Find(TabScrollViewerTheme.ScrollMenuIndicatorPart); - _startEdgeIndicator = e.NameScope.Find(TabScrollViewerTheme.ScrollStartEdgeIndicatorPart); - _endEdgeIndicator = e.NameScope.Find(TabScrollViewerTheme.ScrollEndEdgeIndicatorPart); + _menuIndicator = e.NameScope.Find(BaseTabScrollViewerTheme.ScrollMenuIndicatorPart); + _startEdgeIndicator = e.NameScope.Find(BaseTabScrollViewerTheme.ScrollStartEdgeIndicatorPart); + _endEdgeIndicator = e.NameScope.Find(BaseTabScrollViewerTheme.ScrollEndEdgeIndicatorPart); TokenResourceBinder.CreateTokenBinding(this, EdgeShadowStartColorProperty, GlobalResourceKey.ColorFillSecondary); TokenResourceBinder.CreateTokenBinding(this, MenuEdgeThicknessProperty, TabControlResourceKey.MenuEdgeThickness); - - if (_menuIndicator is not null) { - _menuIndicator.Click += HandleMenuIndicator; - } SetupIndicatorsVisibility(); } - private void HandleMenuIndicator(object? sender, RoutedEventArgs args) - { - if (_menuFlyout is null) { - _menuFlyout = new MenuFlyout(); - } - - if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { - _menuFlyout.Placement = PlacementMode.Bottom; - } else { - _menuFlyout.Placement = PlacementMode.Right; - } - - // 收集没有完全显示的 Tab 列表 - _menuFlyout.Items.Clear(); - if (TabStrip is not null) { - for (int i = 0; i < TabStrip.ItemCount; i++) { - var itemContainer = TabStrip.ContainerFromIndex(i)!; - if (itemContainer is TabStripItem tabStripItem) { - var itemBounds = itemContainer.Bounds; - var left = Math.Floor(itemBounds.Left - Offset.X); - var right = Math.Floor(itemBounds.Right - Offset.X); - if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { - if (left < 0 || right > Viewport.Width) { - var menuItem = new OverflowTabMenuItem() - { - Header = tabStripItem.Content, - TabStripItem = tabStripItem, - IsClosable = tabStripItem.IsClosable - }; - menuItem.Click += HandleMenuItemClicked; - menuItem.CloseTab += HandleCloseTabRequest; - _menuFlyout.Items.Add(menuItem); - } - } - } - } - - if (_menuFlyout.Items.Count > 0) { - _menuFlyout.ShowAt(_menuIndicator!); - } - } - } - - private void HandleMenuItemClicked(object? sender, RoutedEventArgs args) - { - if (TabStrip is not null) { - Dispatcher.UIThread.Post(sender => - { - if (sender is OverflowTabMenuItem tabStripMenuItem) { - var tabStripItem = tabStripMenuItem.TabStripItem; - if (tabStripItem is not null) { - tabStripItem.BringIntoView(); - TabStrip.SelectedItem = tabStripItem; - } - } - }, sender); - } - } - - private void HandleCloseTabRequest(object? sender, RoutedEventArgs args) - { - if (sender is OverflowTabMenuItem tabStripMenuItem) { - if (TabStrip is not null) { - if (TabStrip.SelectedItem is TabStripItem selectedItem) { - if (selectedItem == tabStripMenuItem.TabStripItem) { - var selectedIndex = TabStrip.SelectedIndex; - object? newSelectedItem = null; - if (selectedIndex != 0) { - newSelectedItem = TabStrip.Items[--selectedIndex]; - } - TabStrip.Items.Remove(tabStripMenuItem.TabStripItem); - TabStrip.SelectedItem = newSelectedItem; - } else { - TabStrip.Items.Remove(tabStripMenuItem.TabStripItem); - } - } - } - - } - } - protected override Size MeasureOverride(Size availableSize) { var size = base.MeasureOverride(availableSize); diff --git a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs new file mode 100644 index 0000000..ba91a98 --- /dev/null +++ b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs @@ -0,0 +1,6 @@ +namespace AtomUI.Controls; + +internal class TabControlScrollViewer : BaseTabScrollViewer +{ + +} \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollContentPresenter.cs b/src/AtomUI.Controls/TabControl/TabScrollContentPresenter.cs similarity index 93% rename from src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollContentPresenter.cs rename to src/AtomUI.Controls/TabControl/TabScrollContentPresenter.cs index 70d3356..ad4b8ff 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollContentPresenter.cs +++ b/src/AtomUI.Controls/TabControl/TabScrollContentPresenter.cs @@ -8,13 +8,13 @@ using Avalonia.Rendering; namespace AtomUI.Controls; -internal class TabStripScrollContentPresenter : ScrollContentPresenter, ICustomHitTest +internal class TabScrollContentPresenter : ScrollContentPresenter, ICustomHitTest { internal Dock TabStripPlacement { get; set; } = Dock.Top; private static readonly MethodInfo SnapOffsetMethodInfo; - static TabStripScrollContentPresenter() + static TabScrollContentPresenter() { SnapOffsetMethodInfo = typeof(ScrollContentPresenter).GetMethod("SnapOffset", BindingFlags.Instance | BindingFlags.NonPublic)!; diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs new file mode 100644 index 0000000..3d5d8b1 --- /dev/null +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs @@ -0,0 +1,104 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Threading; + +namespace AtomUI.Controls; + +internal class TabStripScrollViewer : BaseTabScrollViewer +{ + #region 内部属性定义 + + internal BaseTabStrip? TabStrip { get; set; } + + #endregion + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + if (_menuIndicator is not null) { + _menuIndicator.Click += HandleMenuIndicator; + } + } + + private void HandleMenuIndicator(object? sender, RoutedEventArgs args) + { + if (_menuFlyout is null) { + _menuFlyout = new MenuFlyout(); + } + + if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + _menuFlyout.Placement = PlacementMode.Bottom; + } else { + _menuFlyout.Placement = PlacementMode.Right; + } + + // 收集没有完全显示的 Tab 列表 + _menuFlyout.Items.Clear(); + if (TabStrip is not null) { + for (int i = 0; i < TabStrip.ItemCount; i++) { + var itemContainer = TabStrip.ContainerFromIndex(i)!; + if (itemContainer is TabStripItem tabStripItem) { + var itemBounds = itemContainer.Bounds; + var left = Math.Floor(itemBounds.Left - Offset.X); + var right = Math.Floor(itemBounds.Right - Offset.X); + if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + if (left < 0 || right > Viewport.Width) { + var menuItem = new OverflowTabMenuItem() + { + Header = tabStripItem.Content, + TabStripItem = tabStripItem, + IsClosable = tabStripItem.IsClosable + }; + menuItem.Click += HandleMenuItemClicked; + menuItem.CloseTab += HandleCloseTabRequest; + _menuFlyout.Items.Add(menuItem); + } + } + } + } + + if (_menuFlyout.Items.Count > 0) { + _menuFlyout.ShowAt(_menuIndicator!); + } + } + } + + private void HandleMenuItemClicked(object? sender, RoutedEventArgs args) + { + if (TabStrip is not null) { + Dispatcher.UIThread.Post(sender => + { + if (sender is OverflowTabMenuItem tabStripMenuItem) { + var tabStripItem = tabStripMenuItem.TabStripItem; + if (tabStripItem is not null) { + tabStripItem.BringIntoView(); + TabStrip.SelectedItem = tabStripItem; + } + } + }, sender); + } + } + + private void HandleCloseTabRequest(object? sender, RoutedEventArgs args) + { + if (sender is OverflowTabMenuItem tabStripMenuItem) { + if (TabStrip is not null) { + if (TabStrip.SelectedItem is TabStripItem selectedItem) { + if (selectedItem == tabStripMenuItem.TabStripItem) { + var selectedIndex = TabStrip.SelectedIndex; + object? newSelectedItem = null; + if (selectedIndex != 0) { + newSelectedItem = TabStrip.Items[--selectedIndex]; + } + TabStrip.Items.Remove(tabStripMenuItem.TabStripItem); + TabStrip.SelectedItem = newSelectedItem; + } else { + TabStrip.Items.Remove(tabStripMenuItem.TabStripItem); + } + } + } + + } + } +} \ No newline at end of file From fe7ea95236ef583ed166efbfb66ecd4acf4ab0b9 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 13:17:07 +0800 Subject: [PATCH 13/28] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20BaseTabControl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成基础的 BaseTabControl 类,支持两个风格 --- .../TabControl/BaseTabControl.cs | 124 ++++++++++++++- .../TabControl/BaseTabControlTheme.cs | 143 ++++++++++++++++++ 2 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs diff --git a/src/AtomUI.Controls/TabControl/BaseTabControl.cs b/src/AtomUI.Controls/TabControl/BaseTabControl.cs index b22da25..9f974eb 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControl.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControl.cs @@ -1,6 +1,126 @@ -namespace AtomUI.Controls; +using AtomUI.Data; +using AtomUI.Theme.Data; +using AtomUI.Theme.Styling; +using AtomUI.Utils; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.LogicalTree; +using Avalonia.Media; -public class BaseTabControl +namespace AtomUI.Controls; + +using AvaloniaTabControl = Avalonia.Controls.TabControl; + +public class BaseTabControl : AvaloniaTabControl { + public const string TopPC = ":top"; + public const string RightPC = ":right"; + public const string BottomPC = ":bottom"; + public const string LeftPC = ":left"; + private static readonly FuncTemplate DefaultPanel = + new(() => new StackPanel()); + + #region 公共属性定义 + public static readonly StyledProperty SizeTypeProperty = + AvaloniaProperty.Register(nameof(SizeType), SizeType.Middle); + + public static readonly StyledProperty TabAlignmentCenterProperty = + AvaloniaProperty.Register(nameof(TabAlignmentCenter), false); + + public SizeType SizeType + { + get => GetValue(SizeTypeProperty); + set => SetValue(SizeTypeProperty, value); + } + + public bool TabAlignmentCenter + { + get => GetValue(TabAlignmentCenterProperty); + set => SetValue(TabAlignmentCenterProperty, value); + } + + #endregion + + private Border? _frameDecorator; + + static BaseTabControl() + { + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _frameDecorator = e.NameScope.Find(BaseTabControlTheme.FrameDecoratorPart); + SetupBorderBinding(); + } + + private void SetupBorderBinding() + { + if (_frameDecorator is not null) { + TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, + new RenderScaleAwareThicknessConfigure(this)); + } + } + + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is TabItem tabItem) { + BindUtils.RelayBind(this, SizeTypeProperty, tabItem, TabItem.SizeTypeProperty); + } + } + + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnAttachedToLogicalTree(e); + UpdatePseudoClasses(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change.Property == TabStripPlacementProperty) { + UpdatePseudoClasses(); + } + } + + private void UpdatePseudoClasses() + { + PseudoClasses.Set(TopPC, TabStripPlacement == Dock.Top); + PseudoClasses.Set(RightPC, TabStripPlacement == Dock.Right); + PseudoClasses.Set(BottomPC, TabStripPlacement == Dock.Bottom); + PseudoClasses.Set(LeftPC, TabStripPlacement == Dock.Left); + } + + public override void Render(DrawingContext context) + { + Point startPoint = default; + Point endPoint = default; + var borderThickness = BorderThickness.Left; + var offsetDelta = borderThickness / 2; + if (TabStripPlacement == Dock.Top) { + startPoint = new Point(0, Bounds.Height - offsetDelta); + endPoint = new Point(Bounds.Width, Bounds.Height - offsetDelta); + } else if (TabStripPlacement == Dock.Right) { + startPoint = new Point(offsetDelta, 0); + endPoint = new Point(offsetDelta, Bounds.Height); + } else if (TabStripPlacement == Dock.Bottom) { + startPoint = new Point(0, offsetDelta); + endPoint = new Point(Bounds.Width, offsetDelta); + } else { + startPoint = new Point(Bounds.Width - offsetDelta, 0); + endPoint = new Point(Bounds.Width - offsetDelta, Bounds.Height); + } + + using var optionState = context.PushRenderOptions(new RenderOptions() + { + EdgeMode = EdgeMode.Aliased + }); + context.DrawLine(new Pen(BorderBrush, borderThickness), startPoint, endPoint); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs new file mode 100644 index 0000000..f2572c2 --- /dev/null +++ b/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs @@ -0,0 +1,143 @@ +using AtomUI.Theme; +using AtomUI.Theme.Styling; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Layout; +using Avalonia.Styling; + +namespace AtomUI.Controls; + +internal class BaseTabControlTheme : BaseControlTheme +{ + public const string FrameDecoratorPart = "PART_FrameDecorator"; + public const string ItemsPresenterPart = "PART_ItemsPresenter"; + public const string MainLayoutContainerPart = "PART_MainLayoutContainer"; + public const string SelectedContentHostPart = "PART_SelectedContentHost"; + + public BaseTabControlTheme(Type targetType) : base(targetType) { } + + protected override IControlTemplate BuildControlTemplate() + { + return new FuncControlTemplate((baseTabControl, scope) => + { + var frameDecorator = new Border() + { + Name = FrameDecoratorPart + }; + frameDecorator.RegisterInNameScope(scope); + var layoutContainer = new DockPanel() + { + Name = MainLayoutContainerPart + }; + + NotifyBuildTabStripTemplate(baseTabControl, scope, layoutContainer); + NotifyBuildContentPresenter(baseTabControl, scope, layoutContainer); + + return frameDecorator; + }); + } + + protected virtual void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container) + { + } + + protected virtual void NotifyBuildContentPresenter(BaseTabControl baseTabControl, INameScope scope, DockPanel container) + { + var contentPresenter = new ContentPresenter + { + Name = SelectedContentHostPart + }; + contentPresenter.RegisterInNameScope(scope); + CreateTemplateParentBinding(contentPresenter, ContentPresenter.MarginProperty, BaseTabControl.PaddingProperty); + CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, BaseTabControl.SelectedContentProperty); + CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, BaseTabControl.SelectedContentTemplateProperty); + CreateTemplateParentBinding(contentPresenter, ContentPresenter.HorizontalContentAlignmentProperty, BaseTabControl.HorizontalContentAlignmentProperty); + CreateTemplateParentBinding(contentPresenter, ContentPresenter.VerticalContentAlignmentProperty, BaseTabControl.VerticalContentAlignmentProperty); + container.Children.Add(contentPresenter); + } + + protected override void BuildStyles() + { + base.BuildStyles(); + var commonStyle = new Style(selector => selector.Nesting()); + commonStyle.Add(BaseTabControl.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + + // 设置 items presenter 是否居中 + // 分为上、右、下、左 + { + // 上 + var topStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.TopPC)); + topStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + topStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Top); + + // 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); + + commonStyle.Add(topStyle); + } + + { + // 右 + var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.RightPC)); + + rightStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Left); + rightStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Stretch); + + // 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); + } + { + // 下 + var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.BottomPC)); + bottomStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + bottomStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Top); + + // 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); + } + { + // 左 + var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.LeftPC)); + + + leftStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Left); + leftStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Stretch); + + // 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); + } + + Add(commonStyle); + } +} \ No newline at end of file From bcd3b8560a8546300cc8f12f20aacbdde1d5316c Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 13:17:34 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20BaseTabItem=20?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现 BaseTabItem 类 --- .../TabControl/BaseTabItemTheme.cs | 36 +++++++++++++++++++ .../TabControl/TabStrip/TabStripItem.cs | 3 -- .../TabControl/TabStrip/TabStripTheme.cs | 4 +-- 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs diff --git a/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs new file mode 100644 index 0000000..e77b956 --- /dev/null +++ b/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs @@ -0,0 +1,36 @@ +using AtomUI.Theme; +using Avalonia.Controls; +using Avalonia.Controls.Templates; + +namespace AtomUI.Controls; + +internal class BaseTabItemTheme : 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 const string ItemCloseButtonPart = "PART_ItemCloseButton"; + + public BaseTabItemTheme(Type targetType) : base(targetType) { } + + protected override IControlTemplate BuildControlTemplate() + { + return new FuncControlTemplate((tabItem, scope) => + { + // 做边框 + var decorator = new Border() + { + Name = DecoratorPart + }; + decorator.RegisterInNameScope(scope); + NotifyBuildControlTemplate(tabItem, scope, decorator); + return decorator; + }); + } + + protected virtual void NotifyBuildControlTemplate(TabItem stripItem, INameScope scope, Border container) + { + + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripItem.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripItem.cs index 9f1b180..4abda39 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStripItem.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripItem.cs @@ -1,16 +1,13 @@ using AtomUI.Controls.Utils; using AtomUI.Icon; using AtomUI.Media; -using AtomUI.Theme.Data; using AtomUI.Theme.Styling; -using AtomUI.Theme.TokenSystem; using AtomUI.Theme.Utils; using AtomUI.Utils; using Avalonia; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Primitives; -using Avalonia.Data; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.LogicalTree; diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs index d3041bd..35cdf06 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs @@ -17,8 +17,8 @@ internal class TabStripTheme : BaseTabStripTheme protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container) { - var tabScrollViewer = new TabScrollViewer(); - CreateTemplateParentBinding(tabScrollViewer, TabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); + var tabScrollViewer = new TabStripScrollViewer(); + CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabStrip = baseTabStrip; From 4cccf438abc4afd79b912ac11ba1b5bd527b2723 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 13:18:02 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E7=9A=84Tab=E5=88=97=E8=A1=A8=E6=BB=9A=E5=8A=A8=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现基础的Tab列表滚动类 --- .../TabControl/BaseTabScrollViewer.cs | 2 - .../TabControl/BaseTabScrollViewerTheme.cs | 230 ++++++++++++++++++ 2 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs diff --git a/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs b/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs index c5bcc0f..1a6b6d2 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs @@ -7,9 +7,7 @@ using Avalonia.Controls.Converters; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; -using Avalonia.Interactivity; using Avalonia.Media; -using Avalonia.Threading; using Colors = Avalonia.Media.Colors; using GradientStop = Avalonia.Media.GradientStop; diff --git a/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs new file mode 100644 index 0000000..168aa8d --- /dev/null +++ b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs @@ -0,0 +1,230 @@ +using AtomUI.Data; +using AtomUI.Theme; +using AtomUI.Theme.Styling; +using AtomUI.Utils; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Input.GestureRecognizers; +using Avalonia.Layout; +using Avalonia.Styling; + +namespace AtomUI.Controls; + +[ControlThemeProvider] +internal class BaseTabScrollViewerTheme : BaseControlTheme +{ + public const string ScrollStartEdgeIndicatorPart = "Part_ScrollStartEdgeIndicator"; + public const string ScrollEndEdgeIndicatorPart = "Part_ScrollEndEdgeIndicator"; + public const string ScrollMenuIndicatorPart = "Part_ScrollMenuIndicator"; + public const string ScrollViewContentPart = "PART_ContentPresenter"; + public const string ScrollViewLayoutPart = "PART_ScrollViewLayout"; + public const string ScrollViewWrapperLayoutPart = "PART_ScrollViewWrapperLayout"; + + public BaseTabScrollViewerTheme() + : base(typeof(BaseTabScrollViewer)) { } + + protected override IControlTemplate BuildControlTemplate() + { + return new FuncControlTemplate((scrollViewer, scope) => + { + scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; + var containerLayout = new Panel() + { + Name = ScrollViewWrapperLayoutPart + }; + + var scrollViewLayout = new DockPanel() + { + Name = ScrollViewLayoutPart + }; + + var menuIndicatorIcon = new PathIcon() + { + Kind = "EllipsisOutlined", + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + + TokenResourceBinder.CreateTokenBinding(menuIndicatorIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSize); + TokenResourceBinder.CreateTokenBinding(menuIndicatorIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSize); + TokenResourceBinder.CreateTokenBinding(menuIndicatorIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorTextSecondary); + + var menuIndicator = new IconButton() + { + Name = ScrollMenuIndicatorPart, + Icon = menuIndicatorIcon + }; + + menuIndicator.RegisterInNameScope(scope); + + var scrollViewContent = CreateScrollContentPresenter(); + + DockPanel.SetDock(scrollViewContent, Dock.Left); + DockPanel.SetDock(menuIndicator, Dock.Right); + + scrollViewLayout.Children.Add(menuIndicator); + scrollViewLayout.Children.Add(scrollViewContent); + + scrollViewContent.RegisterInNameScope(scope); + + var startEdgeIndicator = new Border() + { + Name = ScrollStartEdgeIndicatorPart, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + IsHitTestVisible = false + }; + startEdgeIndicator.RegisterInNameScope(scope); + + containerLayout.Children.Add(startEdgeIndicator); + + var endEdgeIndicator = new Border() + { + HorizontalAlignment = HorizontalAlignment.Right, + VerticalAlignment = VerticalAlignment.Top, + Name = ScrollEndEdgeIndicatorPart, + IsHitTestVisible = false + }; + endEdgeIndicator.RegisterInNameScope(scope); + + containerLayout.Children.Add(endEdgeIndicator); + containerLayout.Children.Add(scrollViewLayout); + + return containerLayout; + }); + } + + private ScrollContentPresenter CreateScrollContentPresenter() + { + var scrollViewContent = new TabScrollContentPresenter() + { + Name = ScrollViewContentPart, + }; + + CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.MarginProperty, + BaseTabScrollViewer.PaddingProperty); + CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsAlignmentProperty, + BaseTabScrollViewer.HorizontalSnapPointsAlignmentProperty); + CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsTypeProperty, + BaseTabScrollViewer.HorizontalSnapPointsTypeProperty); + CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsAlignmentProperty, + BaseTabScrollViewer.VerticalSnapPointsAlignmentProperty); + CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsTypeProperty, + BaseTabScrollViewer.VerticalSnapPointsTypeProperty); + var scrollGestureRecognizer = new ScrollGestureRecognizer(); + BindUtils.RelayBind(scrollViewContent, ScrollContentPresenter.CanHorizontallyScrollProperty, scrollGestureRecognizer, + ScrollGestureRecognizer.CanHorizontallyScrollProperty); + BindUtils.RelayBind(scrollViewContent, ScrollContentPresenter.CanVerticallyScrollProperty, scrollGestureRecognizer, + ScrollGestureRecognizer.CanVerticallyScrollProperty); + + CreateTemplateParentBinding(scrollGestureRecognizer, ScrollGestureRecognizer.IsScrollInertiaEnabledProperty, + BaseTabScrollViewer.IsScrollInertiaEnabledProperty); + scrollViewContent.GestureRecognizers.Add(scrollGestureRecognizer); + + return scrollViewContent; + } + + protected override void BuildStyles() + { + var topPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Top)); + { + var contentPresenterStyle = + new Style(selector => selector.Nesting().Template().OfType()); + contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left); + var menuIndicatorStyle = + new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); + menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right); + menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); + + var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(startEdgeIndicatorStyle); + + var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart)); + endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(endEdgeIndicatorStyle); + + topPlacementStyle.Add(menuIndicatorStyle); + topPlacementStyle.Add(contentPresenterStyle); + } + + Add(topPlacementStyle); + + var rightPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Right)); + + { + var contentPresenterStyle = + new Style(selector => selector.Nesting().Template().OfType()); + contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top); + var menuIndicatorStyle = + new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); + menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom); + menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); + + var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(startEdgeIndicatorStyle); + + var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(endEdgeIndicatorStyle); + + rightPlacementStyle.Add(menuIndicatorStyle); + rightPlacementStyle.Add(contentPresenterStyle); + } + + Add(rightPlacementStyle); + + var bottomPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Bottom)); + + { + var contentPresenterStyle = + new Style(selector => selector.Nesting().Template().OfType()); + contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left); + var menuIndicatorStyle = + new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); + menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right); + menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); + + var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(startEdgeIndicatorStyle); + + var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(endEdgeIndicatorStyle); + + bottomPlacementStyle.Add(menuIndicatorStyle); + bottomPlacementStyle.Add(contentPresenterStyle); + } + + Add(bottomPlacementStyle); + + var leftPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Left)); + + { + var contentPresenterStyle = + new Style(selector => selector.Nesting().Template().OfType()); + contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top); + var menuIndicatorStyle = + new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); + menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom); + menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); + + var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(startEdgeIndicatorStyle); + + var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + topPlacementStyle.Add(endEdgeIndicatorStyle); + + leftPlacementStyle.Add(menuIndicatorStyle); + leftPlacementStyle.Add(contentPresenterStyle); + } + + Add(leftPlacementStyle); + } +} From 07e0b7f23054bcf817842d1bfcb1b5448bfa8f35 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 13:18:49 +0800 Subject: [PATCH 16/28] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20TabControl=20?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现 TabControl 滚动类 --- src/AtomUI.Controls/TabControl/TabControl.cs | 2 +- src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AtomUI.Controls/TabControl/TabControl.cs b/src/AtomUI.Controls/TabControl/TabControl.cs index 183307a..526637a 100644 --- a/src/AtomUI.Controls/TabControl/TabControl.cs +++ b/src/AtomUI.Controls/TabControl/TabControl.cs @@ -1,6 +1,6 @@ namespace AtomUI.Controls; -public class TabControl +public class TabControl : BaseTabControl { } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs index ba91a98..900a51b 100644 --- a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs @@ -2,5 +2,9 @@ internal class TabControlScrollViewer : BaseTabScrollViewer { - + #region 内部属性定义 + + internal BaseTabControl? TabControl { get; set; } + + #endregion } \ No newline at end of file From 18f75e5c297175db1eb16e1edac800d5e8452b27 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 13:19:18 +0800 Subject: [PATCH 17/28] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E9=A3=8E=E6=A0=BC=E7=9A=84=20TabItem=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现基础风格的 TabItem 类 --- src/AtomUI.Controls/TabControl/TabItem.cs | 207 +++++++++++++++- .../TabControl/TabItemTheme.cs | 14 +- .../TabControl/TabScrollViewerTheme.cs | 230 ------------------ 3 files changed, 217 insertions(+), 234 deletions(-) delete mode 100644 src/AtomUI.Controls/TabControl/TabScrollViewerTheme.cs diff --git a/src/AtomUI.Controls/TabControl/TabItem.cs b/src/AtomUI.Controls/TabControl/TabItem.cs index 0899f88..2a0d12f 100644 --- a/src/AtomUI.Controls/TabControl/TabItem.cs +++ b/src/AtomUI.Controls/TabControl/TabItem.cs @@ -1,6 +1,209 @@ -namespace AtomUI.Controls; +using AtomUI.Controls.Utils; +using AtomUI.Icon; +using AtomUI.Media; +using AtomUI.Theme.Styling; +using AtomUI.Theme.Utils; +using AtomUI.Utils; +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.LogicalTree; +using Avalonia.Rendering; -public class TabItem +namespace AtomUI.Controls; + +using AvaloniaTabItem = Avalonia.Controls.TabItem; + +public class TabItem : AvaloniaTabItem, IControlCustomStyle, ICustomHitTest { + #region 公共属性定义 + public static readonly StyledProperty SizeTypeProperty = + BaseTabControl.SizeTypeProperty.AddOwner(); + + public static readonly StyledProperty IconProperty = + AvaloniaProperty.Register(nameof(Icon)); + public static readonly StyledProperty CloseIconProperty = + AvaloniaProperty.Register(nameof(CloseIcon)); + + public static readonly StyledProperty IsClosableProperty = + AvaloniaProperty.Register(nameof(IsClosable)); + + public SizeType SizeType + { + get => GetValue(SizeTypeProperty); + set => SetValue(SizeTypeProperty, value); + } + + public PathIcon? Icon + { + get => GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + public PathIcon? CloseIcon + { + get => GetValue(CloseIconProperty); + set => SetValue(CloseIconProperty, value); + } + + public bool IsClosable + { + get => GetValue(IsClosableProperty); + set => SetValue(IsClosableProperty, value); + } + + #endregion + + #region 内部属性定义 + internal static readonly StyledProperty ShapeProperty = + AvaloniaProperty.Register(nameof(Shape)); + + public TabSharp Shape + { + get => GetValue(ShapeProperty); + set => SetValue(ShapeProperty, value); + } + #endregion + + private StackPanel? _contentLayout; + private IControlCustomStyle _customStyle; + private IconButton? _closeButton; + + public TabItem() + { + _customStyle = this; + } + + private void SetupItemIcon() + { + if (Icon is not null) { + UIStructureUtils.SetTemplateParent(Icon, this); + Icon.Name = BaseTabItemTheme.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); + } + } + } + + private void SetupCloseIcon() + { + if (CloseIcon is null) { + CloseIcon = new PathIcon + { + Kind = "CloseOutlined" + }; + TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM); + TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM); + } + + CloseIcon.SetValue(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center); + + UIStructureUtils.SetTemplateParent(CloseIcon, this); + if (CloseIcon.ThemeType != IconThemeType.TwoTone) { + TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon); + TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover); + TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled); + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _customStyle.HandleTemplateApplied(e.NameScope); + } + + #region IControlCustomStyle 实现 + + void IControlCustomStyle.HandleTemplateApplied(INameScope scope) + { + _contentLayout = scope.Find(BaseTabItemTheme.ContentLayoutPart); + _closeButton = scope.Find(BaseTabItemTheme.ItemCloseButtonPart); + + SetupItemIcon(); + SetupCloseIcon(); + if (Transitions is null) { + var transitions = new Transitions(); + transitions.Add(AnimationUtils.CreateTransition(ForegroundProperty)); + Transitions = transitions; + } + + if (_closeButton is not null) { + _closeButton.Click += HandleCloseRequest; + } + } + #endregion + + private void HandleCloseRequest(object? sender, RoutedEventArgs args) + { + if (Parent is BaseTabControl tabControl) { + if (tabControl.SelectedItem is TabItem selectedItem) { + if (selectedItem == this) { + var selectedIndex = tabControl.SelectedIndex; + object? newSelectedItem = null; + if (selectedIndex != 0) { + newSelectedItem = tabControl.Items[--selectedIndex]; + } + tabControl.Items.Remove(this); + tabControl.SelectedItem = newSelectedItem; + } else { + tabControl.Items.Remove(this); + } + } + } + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (VisualRoot is not null) { + if (change.Property == IconProperty) { + var oldIcon = change.GetOldValue(); + if (oldIcon != null) { + UIStructureUtils.SetTemplateParent(oldIcon, null); + } + SetupItemIcon(); + } + } + if (change.Property == CloseIconProperty) { + var oldIcon = change.GetOldValue(); + if (oldIcon != null) { + UIStructureUtils.SetTemplateParent(oldIcon, null); + } + SetupCloseIcon(); + } + + if (change.Property == ShapeProperty) { + HandleShapeChanged(); + } + } + + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnAttachedToLogicalTree(e); + HandleShapeChanged(); + } + + private void HandleShapeChanged() + { + if (Shape == TabSharp.Line) { + TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, TabItemTheme.ID); + } else { + TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, CardTabItemTheme.ID); + } + } + + public bool HitTest(Point point) + { + return true; + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabItemTheme.cs b/src/AtomUI.Controls/TabControl/TabItemTheme.cs index 45af512..c780ed4 100644 --- a/src/AtomUI.Controls/TabControl/TabItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabItemTheme.cs @@ -1,6 +1,16 @@ -namespace AtomUI.Controls; +using AtomUI.Theme.Styling; -public class TabItemTheme +namespace AtomUI.Controls; + +[ControlThemeProvider] +internal class TabItemTheme : BaseTabItemTheme { + public const string ID = "TabItem"; + public TabItemTheme() : base(typeof(TabItem)) { } + + public override string ThemeResourceKey() + { + return ID; + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabScrollViewerTheme.cs b/src/AtomUI.Controls/TabControl/TabScrollViewerTheme.cs deleted file mode 100644 index 01ee66a..0000000 --- a/src/AtomUI.Controls/TabControl/TabScrollViewerTheme.cs +++ /dev/null @@ -1,230 +0,0 @@ -using AtomUI.Data; -using AtomUI.Theme; -using AtomUI.Theme.Styling; -using AtomUI.Utils; -using Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.Input.GestureRecognizers; -using Avalonia.Layout; -using Avalonia.Styling; - -namespace AtomUI.Controls; - -[ControlThemeProvider] -internal class TabScrollViewerTheme : BaseControlTheme -{ - public const string ScrollStartEdgeIndicatorPart = "Part_ScrollStartEdgeIndicator"; - public const string ScrollEndEdgeIndicatorPart = "Part_ScrollEndEdgeIndicator"; - public const string ScrollMenuIndicatorPart = "Part_ScrollMenuIndicator"; - public const string ScrollViewContentPart = "PART_ContentPresenter"; - public const string ScrollViewLayoutPart = "PART_ScrollViewLayout"; - public const string ScrollViewWrapperLayoutPart = "PART_ScrollViewWrapperLayout"; - - public TabScrollViewerTheme() - : base(typeof(TabScrollViewer)) { } - - protected override IControlTemplate BuildControlTemplate() - { - return new FuncControlTemplate((scrollViewer, scope) => - { - scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; - var containerLayout = new Panel() - { - Name = ScrollViewWrapperLayoutPart - }; - - var scrollViewLayout = new DockPanel() - { - Name = ScrollViewLayoutPart - }; - - var menuIndicatorIcon = new PathIcon() - { - Kind = "EllipsisOutlined", - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }; - - TokenResourceBinder.CreateTokenBinding(menuIndicatorIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSize); - TokenResourceBinder.CreateTokenBinding(menuIndicatorIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSize); - TokenResourceBinder.CreateTokenBinding(menuIndicatorIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorTextSecondary); - - var menuIndicator = new IconButton() - { - Name = ScrollMenuIndicatorPart, - Icon = menuIndicatorIcon - }; - - menuIndicator.RegisterInNameScope(scope); - - var scrollViewContent = CreateScrollContentPresenter(); - - DockPanel.SetDock(scrollViewContent, Dock.Left); - DockPanel.SetDock(menuIndicator, Dock.Right); - - scrollViewLayout.Children.Add(menuIndicator); - scrollViewLayout.Children.Add(scrollViewContent); - - scrollViewContent.RegisterInNameScope(scope); - - var startEdgeIndicator = new Border() - { - Name = ScrollStartEdgeIndicatorPart, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - IsHitTestVisible = false - }; - startEdgeIndicator.RegisterInNameScope(scope); - - containerLayout.Children.Add(startEdgeIndicator); - - var endEdgeIndicator = new Border() - { - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Top, - Name = ScrollEndEdgeIndicatorPart, - IsHitTestVisible = false - }; - endEdgeIndicator.RegisterInNameScope(scope); - - containerLayout.Children.Add(endEdgeIndicator); - containerLayout.Children.Add(scrollViewLayout); - - return containerLayout; - }); - } - - private ScrollContentPresenter CreateScrollContentPresenter() - { - var scrollViewContent = new TabStripScrollContentPresenter() - { - Name = ScrollViewContentPart, - }; - - CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.MarginProperty, - TabScrollViewer.PaddingProperty); - CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsAlignmentProperty, - TabScrollViewer.HorizontalSnapPointsAlignmentProperty); - CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsTypeProperty, - TabScrollViewer.HorizontalSnapPointsTypeProperty); - CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsAlignmentProperty, - TabScrollViewer.VerticalSnapPointsAlignmentProperty); - CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsTypeProperty, - TabScrollViewer.VerticalSnapPointsTypeProperty); - var scrollGestureRecognizer = new ScrollGestureRecognizer(); - BindUtils.RelayBind(scrollViewContent, ScrollContentPresenter.CanHorizontallyScrollProperty, scrollGestureRecognizer, - ScrollGestureRecognizer.CanHorizontallyScrollProperty); - BindUtils.RelayBind(scrollViewContent, ScrollContentPresenter.CanVerticallyScrollProperty, scrollGestureRecognizer, - ScrollGestureRecognizer.CanVerticallyScrollProperty); - - CreateTemplateParentBinding(scrollGestureRecognizer, ScrollGestureRecognizer.IsScrollInertiaEnabledProperty, - TabScrollViewer.IsScrollInertiaEnabledProperty); - scrollViewContent.GestureRecognizers.Add(scrollGestureRecognizer); - - return scrollViewContent; - } - - protected override void BuildStyles() - { - var topPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Top)); - { - var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); - contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left); - var menuIndicatorStyle = - new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); - menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); - - var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(startEdgeIndicatorStyle); - - var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(endEdgeIndicatorStyle); - - topPlacementStyle.Add(menuIndicatorStyle); - topPlacementStyle.Add(contentPresenterStyle); - } - - Add(topPlacementStyle); - - var rightPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Right)); - - { - var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); - contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top); - var menuIndicatorStyle = - new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); - menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); - - var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(startEdgeIndicatorStyle); - - var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(endEdgeIndicatorStyle); - - rightPlacementStyle.Add(menuIndicatorStyle); - rightPlacementStyle.Add(contentPresenterStyle); - } - - Add(rightPlacementStyle); - - var bottomPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Bottom)); - - { - var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); - contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left); - var menuIndicatorStyle = - new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); - menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); - - var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(startEdgeIndicatorStyle); - - var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(endEdgeIndicatorStyle); - - bottomPlacementStyle.Add(menuIndicatorStyle); - bottomPlacementStyle.Add(contentPresenterStyle); - } - - Add(bottomPlacementStyle); - - var leftPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Left)); - - { - var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); - contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top); - var menuIndicatorStyle = - new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); - menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); - - var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(startEdgeIndicatorStyle); - - var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(endEdgeIndicatorStyle); - - leftPlacementStyle.Add(menuIndicatorStyle); - leftPlacementStyle.Add(contentPresenterStyle); - } - - Add(leftPlacementStyle); - } -} From c35afb945f3ed3298ffc6ad8be09b28a8c2a2d4a Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 13:20:12 +0800 Subject: [PATCH 18/28] =?UTF-8?q?=E9=87=8D=E6=9E=84=20TabStrip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构 TabStrip,提炼一些基础功能 --- .../TabControl/TabStrip/BaseTabStrip.cs | 6 +++--- .../TabControl/TabStrip/BaseTabStripItemTheme.cs | 1 - .../TabControl/TabStrip/BaseTabStripTheme.cs | 12 ++++++------ .../TabControl/TabStrip/CardTabStrip.cs | 4 ++-- .../TabControl/TabStrip/CardTabStripTheme.cs | 4 ++-- src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs | 1 - 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStrip.cs index c27d4f9..58cf307 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStrip.cs @@ -54,7 +54,7 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware #endregion - private Border? _mainContainer; + private Border? _frameDecorator; static BaseTabStrip() { @@ -65,13 +65,13 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _mainContainer = e.NameScope.Find(BaseTabStripTheme.MainContainerPart); + _frameDecorator = e.NameScope.Find(BaseTabStripTheme.FrameDecoratorPart); SetupBorderBinding(); } private void SetupBorderBinding() { - if (_mainContainer is not null) { + if (_frameDecorator is not null) { TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, new RenderScaleAwareThicknessConfigure(this)); } diff --git a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs index 835ca33..199771d 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs @@ -2,7 +2,6 @@ using AtomUI.Theme; using AtomUI.Theme.Styling; using AtomUI.Utils; -using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; diff --git a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs index 1480cb3..044ad49 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs @@ -10,7 +10,7 @@ namespace AtomUI.Controls; internal class BaseTabStripTheme : BaseControlTheme { - public const string MainContainerPart = "Part_MainContainer"; + public const string FrameDecoratorPart = "Part_FrameDecorator"; public const string ItemsPresenterPart = "PART_ItemsPresenter"; public BaseTabStripTheme(Type targetType) : base(targetType) { } @@ -19,13 +19,13 @@ internal class BaseTabStripTheme : BaseControlTheme { return new FuncControlTemplate((strip, scope) => { - var mainContainer = new Border() + var frameDecorator = new Border() { - Name = MainContainerPart + Name = FrameDecoratorPart }; - mainContainer.RegisterInNameScope(scope); - NotifyBuildControlTemplate(strip, scope, mainContainer); - return mainContainer; + frameDecorator.RegisterInNameScope(scope); + NotifyBuildControlTemplate(strip, scope, frameDecorator); + return frameDecorator; }); } diff --git a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs index dfc0da0..37018e9 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs @@ -86,7 +86,7 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle private IconButton? _addTabButton; private ItemsPresenter? _itemsPresenter; private Grid? _cardTabStripContainer; - private TabScrollViewer? _tabScrollViewer; + private BaseTabScrollViewer? _tabScrollViewer; public CardTabStrip() { @@ -164,7 +164,7 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle _addTabButton = scope.Find(CardTabStripTheme.AddTabButtonPart); _itemsPresenter = scope.Find(CardTabStripTheme.ItemsPresenterPart); _cardTabStripContainer = scope.Find(CardTabStripTheme.CardTabStripContainerPart); - _tabScrollViewer = scope.Find(CardTabStripTheme.CardTabStripScrollViewerPart); + _tabScrollViewer = scope.Find(CardTabStripTheme.CardTabStripScrollViewerPart); if (_addTabButton is not null) { _addTabButton.Click += HandleAddButtonClicked; } diff --git a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs index 7e5ddc6..7d66141 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs @@ -29,12 +29,12 @@ internal class CardTabStripTheme : BaseTabStripTheme TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); - var tabScrollViewer = new TabScrollViewer() + var tabScrollViewer = new TabStripScrollViewer() { Name = CardTabStripScrollViewerPart }; tabScrollViewer.RegisterInNameScope(scope); - CreateTemplateParentBinding(tabScrollViewer, TabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); + CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabStrip = baseTabStrip; diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs index 8d38a75..1555ba1 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs @@ -7,7 +7,6 @@ using Avalonia.Animation.Easings; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; -using Avalonia.Layout; using Avalonia.Media.Transformation; namespace AtomUI.Controls; From d6a98a90147175234588cabbe7b9903efe795832 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 13:20:36 +0800 Subject: [PATCH 19/28] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20TabControlTheme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现 TabControlTheme --- .../ShowCase/TabControlShowCase.axaml | 15 +++++++ .../TabControl/CardTabControl.cs | 6 +++ .../TabControl/CardTabControlTheme.cs | 9 ++++ .../TabControl/CardTabItemTheme.cs | 16 +++++++ .../TabControl/TabControlTheme.cs | 44 ++++++++++++++++++- 5 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/AtomUI.Controls/TabControl/CardTabControl.cs create mode 100644 src/AtomUI.Controls/TabControl/CardTabControlTheme.cs create mode 100644 src/AtomUI.Controls/TabControl/CardTabItemTheme.cs diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index cad3905..2db4eb6 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -8,6 +8,21 @@ xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase" mc:Ignorable="d"> + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + Date: Sat, 3 Aug 2024 20:23:49 +0800 Subject: [PATCH 20/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8DTabStrip=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E7=BB=84=E4=BB=B6=E6=97=A0=E6=B3=95=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=A8=A1=E6=9D=BF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 提取共同的父类之后没有给子类指定需要读取的模板资源所以造成 controlTheme 无法正确加载的问题 --- .../TabControl/BaseTabScrollViewerTheme.cs | 34 +++++++++---------- .../TabControl/TabControlScrollViewer.cs | 2 ++ .../TabControl/TabStrip/CardTabStrip.cs | 7 ++-- .../TabStrip/TabStripScrollViewer.cs | 2 ++ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs index 168aa8d..89ec163 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs @@ -16,11 +16,11 @@ namespace AtomUI.Controls; internal class BaseTabScrollViewerTheme : BaseControlTheme { public const string ScrollStartEdgeIndicatorPart = "Part_ScrollStartEdgeIndicator"; - public const string ScrollEndEdgeIndicatorPart = "Part_ScrollEndEdgeIndicator"; - public const string ScrollMenuIndicatorPart = "Part_ScrollMenuIndicator"; - public const string ScrollViewContentPart = "PART_ContentPresenter"; - public const string ScrollViewLayoutPart = "PART_ScrollViewLayout"; - public const string ScrollViewWrapperLayoutPart = "PART_ScrollViewWrapperLayout"; + public const string ScrollEndEdgeIndicatorPart = "Part_ScrollEndEdgeIndicator"; + public const string ScrollMenuIndicatorPart = "Part_ScrollMenuIndicator"; + public const string ScrollViewContentPart = "PART_ContentPresenter"; + public const string ScrollViewLayoutPart = "PART_ScrollViewLayout"; + public const string ScrollViewWrapperLayoutPart = "PART_ScrollViewWrapperLayout"; public BaseTabScrollViewerTheme() : base(typeof(BaseTabScrollViewer)) { } @@ -136,14 +136,14 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); + menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + startEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(startEdgeIndicatorStyle); var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + endEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(endEdgeIndicatorStyle); topPlacementStyle.Add(menuIndicatorStyle); @@ -161,14 +161,14 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); + menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + startEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(startEdgeIndicatorStyle); var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + endEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(endEdgeIndicatorStyle); rightPlacementStyle.Add(menuIndicatorStyle); @@ -186,14 +186,14 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); + menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal); var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + startEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(startEdgeIndicatorStyle); var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + endEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(endEdgeIndicatorStyle); bottomPlacementStyle.Add(menuIndicatorStyle); @@ -211,14 +211,14 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom); - menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); + menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical); var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + startEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(startEdgeIndicatorStyle); var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); - endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness); + endEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); topPlacementStyle.Add(endEdgeIndicatorStyle); leftPlacementStyle.Add(menuIndicatorStyle); diff --git a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs index 900a51b..88455b3 100644 --- a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs @@ -7,4 +7,6 @@ internal class TabControlScrollViewer : BaseTabScrollViewer internal BaseTabControl? TabControl { get; set; } #endregion + + protected override Type StyleKeyOverride => typeof(BaseTabScrollViewer); } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs index 37018e9..0bf0431 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Interactivity; +using Avalonia.Layout; namespace AtomUI.Controls; @@ -260,14 +261,16 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle } else if (TabStripPlacement == Dock.Left) { CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:0); if (_addTabButton is not null && _itemsPresenter is not null) { - _addTabButton.Width = _itemsPresenter.DesiredSize.Width; + _addTabButton.Width = _cardSize; _addTabButton.Height = _cardSize; + _addTabButton.HorizontalAlignment = HorizontalAlignment.Right; } } else { CardBorderRadius = new CornerRadius(topLeft: 0, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:_cardBorderRadiusSize.BottomRight); if (_addTabButton is not null && _itemsPresenter is not null) { - _addTabButton.Width = _itemsPresenter.DesiredSize.Width; + _addTabButton.Width = _cardSize; _addTabButton.Height = _cardSize; + _addTabButton.HorizontalAlignment = HorizontalAlignment.Left; } } } diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs index 3d5d8b1..4a1c07b 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs @@ -12,6 +12,8 @@ internal class TabStripScrollViewer : BaseTabScrollViewer internal BaseTabStrip? TabStrip { get; set; } #endregion + + protected override Type StyleKeyOverride => typeof(BaseTabScrollViewer); protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { From 055c973210845b2ac9a2c54f5413f566c02eefd5 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 22:13:01 +0800 Subject: [PATCH 21/28] =?UTF-8?q?=E5=9F=BA=E7=A1=80=20TabControl=20?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E6=A0=B7=E5=BC=8F=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCase/TabControlShowCase.axaml | 6 +- .../TokenResourceConst.g.cs | 1 + .../TabControl/BaseTabControl.cs | 47 ++++- .../TabControl/BaseTabControlTheme.cs | 3 +- .../TabControl/BaseTabItemTheme.cs | 165 +++++++++++++++++- src/AtomUI.Controls/TabControl/TabControl.cs | 120 ++++++++++++- .../TabControl/TabControlTheme.cs | 88 +++++++++- .../TabControl/TabControlToken.cs | 6 + .../TabControl/TabItemTheme.cs | 81 +++++++++ .../TabControl/TabStrip/TabStrip.cs | 8 +- 10 files changed, 514 insertions(+), 11 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 2db4eb6..de3d05e 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -15,9 +15,9 @@ Description="Default activate first tab."> - Tab 1 - Tab 2 - Tab 3 + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 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 4eb6789..9364d37 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 @@ -321,6 +321,7 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey AddTabButtonMarginHorizontal = new TokenResourceKey("TabControl.AddTabButtonMarginHorizontal"); public static readonly TokenResourceKey AddTabButtonMarginVertical = new TokenResourceKey("TabControl.AddTabButtonMarginVertical"); public static readonly TokenResourceKey CloseIconMargin = new TokenResourceKey("TabControl.CloseIconMargin"); + public static readonly TokenResourceKey TabAndContentGutter = new TokenResourceKey("TabControl.TabAndContentGutter"); } public static class TagResourceKey diff --git a/src/AtomUI.Controls/TabControl/BaseTabControl.cs b/src/AtomUI.Controls/TabControl/BaseTabControl.cs index 9f974eb..2311cab 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControl.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControl.cs @@ -43,6 +43,34 @@ public class BaseTabControl : AvaloniaTabControl set => SetValue(TabAlignmentCenterProperty, value); } + #endregion + + #region 内部属性实现 + + internal static readonly DirectProperty TabAndContentGutterProperty = + AvaloniaProperty.RegisterDirect(nameof(TabAndContentGutter), + o => o.TabAndContentGutter, + (o, v) => o.TabAndContentGutter = v); + + private double _tabAndContentGutter; + internal double TabAndContentGutter + { + get => _tabAndContentGutter; + set => SetAndRaise(TabAndContentGutterProperty, ref _tabAndContentGutter, value); + } + + internal static readonly DirectProperty TabStripMarginProperty = + AvaloniaProperty.RegisterDirect(nameof(TabStripMargin), + o => o.TabStripMargin, + (o, v) => o.TabStripMargin = v); + + private Thickness _tabStripMargin; + internal Thickness TabStripMargin + { + get => _tabStripMargin; + set => SetAndRaise(TabStripMarginProperty, ref _tabStripMargin, value); + } + #endregion private Border? _frameDecorator; @@ -57,6 +85,7 @@ public class BaseTabControl : AvaloniaTabControl base.OnApplyTemplate(e); _frameDecorator = e.NameScope.Find(BaseTabControlTheme.FrameDecoratorPart); SetupBorderBinding(); + HandlePlacementChanged(); } private void SetupBorderBinding() @@ -65,6 +94,8 @@ public class BaseTabControl : AvaloniaTabControl TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, new RenderScaleAwareThicknessConfigure(this)); } + + TokenResourceBinder.CreateTokenBinding(this, TabAndContentGutterProperty, TabControlResourceKey.TabAndContentGutter); } protected override void PrepareContainerForItemOverride(Control container, object? item, int index) @@ -85,7 +116,7 @@ public class BaseTabControl : AvaloniaTabControl { base.OnPropertyChanged(change); if (change.Property == TabStripPlacementProperty) { - UpdatePseudoClasses(); + HandlePlacementChanged(); } } @@ -97,6 +128,20 @@ public class BaseTabControl : AvaloniaTabControl PseudoClasses.Set(LeftPC, TabStripPlacement == Dock.Left); } + private void HandlePlacementChanged() + { + UpdatePseudoClasses(); + if (TabStripPlacement == Dock.Top) { + TabStripMargin = new Thickness(0, 0, 0, _tabAndContentGutter); + } else if (TabStripPlacement == Dock.Right) { + TabStripMargin = new Thickness(_tabAndContentGutter, 0, 0, 0); + } else if (TabStripPlacement == Dock.Bottom) { + TabStripMargin = new Thickness(0, _tabAndContentGutter, 0, 0); + } else { + TabStripMargin = new Thickness(0, 0, _tabAndContentGutter, 0); + } + } + public override void Render(DrawingContext context) { Point startPoint = default; diff --git a/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs index f2572c2..21505e1 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs @@ -14,6 +14,7 @@ internal class BaseTabControlTheme : BaseControlTheme public const string ItemsPresenterPart = "PART_ItemsPresenter"; public const string MainLayoutContainerPart = "PART_MainLayoutContainer"; public const string SelectedContentHostPart = "PART_SelectedContentHost"; + public const string TabsContainerPart = "PART_TabsContainer"; public BaseTabControlTheme(Type targetType) : base(targetType) { } @@ -33,7 +34,7 @@ internal class BaseTabControlTheme : BaseControlTheme NotifyBuildTabStripTemplate(baseTabControl, scope, layoutContainer); NotifyBuildContentPresenter(baseTabControl, scope, layoutContainer); - + frameDecorator.Child = layoutContainer; return frameDecorator; }); } diff --git a/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs index e77b956..3d3753a 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs @@ -1,6 +1,13 @@ -using AtomUI.Theme; +using AtomUI.Icon; +using AtomUI.Theme; +using AtomUI.Theme.Styling; +using AtomUI.Utils; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Input; +using Avalonia.Layout; +using Avalonia.Styling; namespace AtomUI.Controls; @@ -29,8 +36,162 @@ internal class BaseTabItemTheme : BaseControlTheme }); } - protected virtual void NotifyBuildControlTemplate(TabItem stripItem, INameScope scope, Border container) + protected virtual void NotifyBuildControlTemplate(TabItem tabItem, INameScope scope, Border container) { + var containerLayout = new StackPanel() + { + Name = ContentLayoutPart, + Orientation = Orientation.Horizontal + }; + containerLayout.RegisterInNameScope(scope); + var contentPresenter = new ContentPresenter() + { + Name = ContentPresenterPart + }; + containerLayout.Children.Add(contentPresenter); + CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, TabItem.HeaderProperty); + CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, TabItem.HeaderTemplateProperty); + + var iconButton = new IconButton() + { + Name = ItemCloseButtonPart + }; + iconButton.RegisterInNameScope(scope); + TokenResourceBinder.CreateTokenBinding(iconButton, IconButton.MarginProperty, TabControlResourceKey.CloseIconMargin); + + CreateTemplateParentBinding(iconButton, IconButton.IconProperty, TabItem.CloseIconProperty); + CreateTemplateParentBinding(iconButton, IconButton.IsVisibleProperty, TabItem.IsClosableProperty); + + containerLayout.Children.Add(iconButton); + container.Child = containerLayout; + } + + protected override void BuildStyles() + { + var commonStyle = new Style(selector => selector.Nesting()); + commonStyle.Add(TabItem.CursorProperty, new Cursor(StandardCursorType.Hand)); + commonStyle.Add(TabItem.ForegroundProperty, TabControlResourceKey.ItemColor); + + // Icon 一些通用属性 + { + var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart)); + iconStyle.Add(PathIcon.MarginProperty, TabControlResourceKey.ItemIconMargin); + commonStyle.Add(iconStyle); + } + + // hover + var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); + hoverStyle.Add(TabItem.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(TabItem.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(); + BuildPlacementStyle(); + BuildDisabledStyle(); + } + + private void BuildSizeTypeStyle() + { + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large)); + + largeSizeStyle.Add(TabItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeLG); + { + // 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(TabItem.SizeTypeProperty, SizeType.Middle)); + { + // 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); + } + middleSizeStyle.Add(TabItem.FontSizeProperty, TabControlResourceKey.TitleFontSize); + Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small)); + + { + // 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); + } + + smallSizeType.Add(TabItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeSM); + Add(smallSizeType); + } + + private void BuildPlacementStyle() + { + // 设置 items presenter 面板样式 + // 分为上、右、下、左 + { + // 上 + var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.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(TabItem.TabStripPlacementProperty, Dock.Right)); + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left); + iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center); + rightStyle.Add(iconStyle); + Add(rightStyle); + } + { + // 下 + var bottomStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.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(TabItem.TabStripPlacementProperty, Dock.Left)); + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center); + iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left); + leftStyle.Add(iconStyle); + Add(leftStyle); + } + } + + private void BuildDisabledStyle() + { + var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled)); + disabledStyle.Add(TabItem.ForegroundProperty, GlobalResourceKey.ColorTextDisabled); + Add(disabledStyle); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabControl.cs b/src/AtomUI.Controls/TabControl/TabControl.cs index 526637a..e751e26 100644 --- a/src/AtomUI.Controls/TabControl/TabControl.cs +++ b/src/AtomUI.Controls/TabControl/TabControl.cs @@ -1,6 +1,124 @@ -namespace AtomUI.Controls; +using AtomUI.Theme.Styling; +using AtomUI.Theme.Utils; +using AtomUI.Utils; +using Avalonia; +using Avalonia.Animation; +using Avalonia.Animation.Easings; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Media.Transformation; + +namespace AtomUI.Controls; public class TabControl : BaseTabControl { + #region 内部属性定义 + + internal static readonly DirectProperty SelectedIndicatorThicknessProperty = + AvaloniaProperty.RegisterDirect(nameof(SelectedIndicatorThickness), + o => o.SelectedIndicatorThickness, + (o, v) => o.SelectedIndicatorThickness = v); + + private double _selectedIndicatorThickness; + internal double SelectedIndicatorThickness + { + get => _selectedIndicatorThickness; + set => SetAndRaise(SelectedIndicatorThicknessProperty, ref _selectedIndicatorThickness, value); + } + + #endregion + private Border? _selectedIndicator; + private ItemsPresenter? _itemsPresenter; + + public TabControl() + { + SelectionChanged += HandleSelectionChanged; + LayoutUpdated += HandleLayoutUpdated; + } + + private void HandleSelectionChanged(object? sender, SelectionChangedEventArgs args) + { + if (VisualRoot is not null) { + SetupSelectedIndicator(); + } + } + + 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 TabItem tabStripItem) { + var selectedBounds = tabStripItem.Bounds; + var builder = new TransformOperations.Builder(1); + var offset = _itemsPresenter?.Bounds.Position ?? default; + + if (TabStripPlacement == Dock.Top) { + _selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width); + _selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness); + builder.AppendTranslate(offset.X + selectedBounds.Left, 0); + } else if (TabStripPlacement == Dock.Right) { + _selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height); + _selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness); + builder.AppendTranslate(0, offset.Y + selectedBounds.Y); + } else if (TabStripPlacement == Dock.Bottom) { + _selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width); + _selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness); + builder.AppendTranslate(offset.X + selectedBounds.Left, 0); + } else { + _selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height); + _selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness); + builder.AppendTranslate(0, offset.Y + selectedBounds.Y); + } + _selectedIndicator.RenderTransform = builder.Build(); + } + } + + protected override Size ArrangeOverride(Size finalSize) + { + var size = base.ArrangeOverride(finalSize); + if (SelectedItem is TabItem) { + SetupSelectedIndicator(); + } + return size; + } + + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) + { + var tabItem = new TabItem + { + Shape = TabSharp.Line + }; + return tabItem; + } + + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is TabItem tabItem) { + tabItem.Shape = TabSharp.Line; + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _selectedIndicator = e.NameScope.Find(TabControlTheme.SelectedItemIndicatorPart); + _itemsPresenter = e.NameScope.Find(TabControlTheme.ItemsPresenterPart); + + TokenResourceBinder.CreateGlobalResourceBinding(this, SelectedIndicatorThicknessProperty, GlobalResourceKey.LineWidthBold); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabControlTheme.cs b/src/AtomUI.Controls/TabControl/TabControlTheme.cs index ba53dad..92f8a2c 100644 --- a/src/AtomUI.Controls/TabControl/TabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabControlTheme.cs @@ -3,6 +3,8 @@ using AtomUI.Utils; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Layout; +using Avalonia.Styling; namespace AtomUI.Controls; @@ -15,11 +17,17 @@ internal class TabControlTheme : BaseTabControlTheme protected override void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container) { - var tabScrollViewer = new TabControlScrollViewer(); + var tabScrollViewer = new TabControlScrollViewer() + { + Name = TabsContainerPart + }; CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabControl = baseTabControl; + CreateTemplateParentBinding(tabScrollViewer, DockPanel.DockProperty, TabControl.TabStripPlacementProperty); + CreateTemplateParentBinding(tabScrollViewer, TabControlScrollViewer.MarginProperty, + TabControl.TabStripMarginProperty); container.Children.Add(tabScrollViewer); } @@ -43,4 +51,82 @@ internal class TabControlTheme : BaseTabControlTheme CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, TabControl.ItemsPanelProperty); return layout; } + + protected override void BuildStyles() + { + base.BuildStyles(); + var commonStyle = new Style(selector => selector.Nesting()); + + // 设置 items presenter 面板样式 + // 分为上、右、下、左 + { + // 上 + var topStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.TopPC)); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + + topStyle.Add(itemPresenterPanelStyle); + + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); + indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); + indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom); + topStyle.Add(indicatorStyle); + + topStyle.Add(itemPresenterPanelStyle); + commonStyle.Add(topStyle); + } + + { + // 右 + var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.RightPC)); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter); + rightStyle.Add(itemPresenterPanelStyle); + + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); + indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); + indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); + rightStyle.Add(indicatorStyle); + + commonStyle.Add(rightStyle); + } + { + // 下 + var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.BottomPC)); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + bottomStyle.Add(itemPresenterPanelStyle); + + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); + indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); + indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); + bottomStyle.Add(indicatorStyle); + + commonStyle.Add(bottomStyle); + } + { + // 左 + var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.LeftPC)); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter); + leftStyle.Add(itemPresenterPanelStyle); + + var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart)); + indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right); + indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); + leftStyle.Add(indicatorStyle); + + commonStyle.Add(leftStyle); + } + + Add(commonStyle); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabControlToken.cs b/src/AtomUI.Controls/TabControl/TabControlToken.cs index 8bace83..770a4d9 100644 --- a/src/AtomUI.Controls/TabControl/TabControlToken.cs +++ b/src/AtomUI.Controls/TabControl/TabControlToken.cs @@ -151,6 +151,11 @@ internal class TabControlToken : AbstractControlDesignToken /// 关闭按钮外边距 /// public Thickness CloseIconMargin { get; set; } + + /// + /// Tab 标签和内容区域的间距 + /// + public double TabAndContentGutter { get; set; } internal override void CalculateFromAlias() { @@ -202,5 +207,6 @@ internal class TabControlToken : AbstractControlDesignToken CloseIconMargin = new Thickness(_globalToken.MarginXXS, 0, 0, 0); MenuEdgeThickness = 20; + TabAndContentGutter = _globalToken.MarginSM; } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabItemTheme.cs b/src/AtomUI.Controls/TabControl/TabItemTheme.cs index c780ed4..756cf55 100644 --- a/src/AtomUI.Controls/TabControl/TabItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabItemTheme.cs @@ -1,4 +1,6 @@ using AtomUI.Theme.Styling; +using Avalonia.Controls; +using Avalonia.Styling; namespace AtomUI.Controls; @@ -13,4 +15,83 @@ internal class TabItemTheme : BaseTabItemTheme { return ID; } + + protected override void BuildStyles() + { + base.BuildStyles(); + BuildSizeTypeStyle(); + } + + protected void BuildSizeTypeStyle() + { + var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Top), + selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Bottom))); + + { + topOrBottomStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin); + + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingLG); + largeSizeStyle.Add(decoratorStyle); + } + + topOrBottomStyle.Add(largeSizeStyle); + + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPadding); + middleSizeStyle.Add(decoratorStyle); + } + + topOrBottomStyle.Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingSM); + smallSizeType.Add(decoratorStyle); + } + + topOrBottomStyle.Add(smallSizeType); + + Add(topOrBottomStyle); + } + + var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Left), + selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Right))); + { + // 貌似没必要分大小,但是先放着吧,万一需要难得再加 + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + largeSizeStyle.Add(decoratorStyle); + } + + leftOrRightStyle.Add(largeSizeStyle); + + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + middleSizeStyle.Add(decoratorStyle); + } + + leftOrRightStyle.Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + smallSizeType.Add(decoratorStyle); + } + + leftOrRightStyle.Add(smallSizeType); + + Add(leftOrRightStyle); + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs index 1555ba1..2bf9557 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStrip.cs @@ -13,10 +13,12 @@ namespace AtomUI.Controls; public class TabStrip : BaseTabStrip { + #region 内部属性定义 + internal static readonly DirectProperty SelectedIndicatorThicknessProperty = AvaloniaProperty.RegisterDirect(nameof(SelectedIndicatorThickness), - o => o.SelectedIndicatorThickness, - (o, v) => o.SelectedIndicatorThickness = v); + o => o.SelectedIndicatorThickness, + (o, v) => o.SelectedIndicatorThickness = v); private double _selectedIndicatorThickness; internal double SelectedIndicatorThickness @@ -24,6 +26,8 @@ public class TabStrip : BaseTabStrip get => _selectedIndicatorThickness; set => SetAndRaise(SelectedIndicatorThicknessProperty, ref _selectedIndicatorThickness, value); } + + #endregion private Border? _selectedIndicator; private ItemsPresenter? _itemsPresenter; From d87ade5a52b134cc729c6b3c3cca16c64ff9277d Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 22:39:22 +0800 Subject: [PATCH 22/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20TabControl=20?= =?UTF-8?q?=E5=88=86=E5=89=B2=E7=BA=BF=E7=BB=98=E5=88=B6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 TabControl 分割线绘制问题 --- .../TabControl/BaseTabControl.cs | 58 +++++++++++++------ .../TabControl/TabControlTheme.cs | 3 +- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/AtomUI.Controls/TabControl/BaseTabControl.cs b/src/AtomUI.Controls/TabControl/BaseTabControl.cs index 2311cab..31ecaad 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControl.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControl.cs @@ -73,7 +73,10 @@ public class BaseTabControl : AvaloniaTabControl #endregion - private Border? _frameDecorator; + private Border? _frameDecorator; + private TabControlScrollViewer? _tabsContainer; + private Point _tabStripBorderStartPoint; + private Point _tabStripBorderEndPoint; static BaseTabControl() { @@ -84,6 +87,7 @@ public class BaseTabControl : AvaloniaTabControl { base.OnApplyTemplate(e); _frameDecorator = e.NameScope.Find(BaseTabControlTheme.FrameDecoratorPart); + _tabsContainer = e.NameScope.Find(BaseTabControlTheme.TabsContainerPart); SetupBorderBinding(); HandlePlacementChanged(); } @@ -142,30 +146,46 @@ public class BaseTabControl : AvaloniaTabControl } } + private void SetupTabStripBorderPoints() + { + if (_tabsContainer is not null) { + var offset = _tabsContainer.TranslatePoint(new Point(0, 0), this) ?? default; + var size = _tabsContainer.Bounds.Size; + var borderThickness = BorderThickness.Left; + var offsetDelta = borderThickness / 2; + if (TabStripPlacement == Dock.Top) { + _tabStripBorderStartPoint = new Point(0, size.Height - offsetDelta); + _tabStripBorderEndPoint = new Point(size.Width, size.Height - offsetDelta); + } else if (TabStripPlacement == Dock.Right) { + _tabStripBorderStartPoint = new Point(offsetDelta, 0); + _tabStripBorderEndPoint = new Point(offsetDelta, size.Height); + } else if (TabStripPlacement == Dock.Bottom) { + _tabStripBorderStartPoint = new Point(0, offsetDelta); + _tabStripBorderEndPoint = new Point(size.Width, offsetDelta); + } else { + _tabStripBorderStartPoint = new Point(size.Width - offsetDelta, 0); + _tabStripBorderEndPoint = new Point(size.Width - offsetDelta, size.Height); + } + + _tabStripBorderStartPoint += offset; + _tabStripBorderEndPoint += offset; + } + } + + protected override Size ArrangeOverride(Size finalSize) + { + var size = base.ArrangeOverride(finalSize); + SetupTabStripBorderPoints(); + return size; + } + public override void Render(DrawingContext context) { - Point startPoint = default; - Point endPoint = default; var borderThickness = BorderThickness.Left; - var offsetDelta = borderThickness / 2; - if (TabStripPlacement == Dock.Top) { - startPoint = new Point(0, Bounds.Height - offsetDelta); - endPoint = new Point(Bounds.Width, Bounds.Height - offsetDelta); - } else if (TabStripPlacement == Dock.Right) { - startPoint = new Point(offsetDelta, 0); - endPoint = new Point(offsetDelta, Bounds.Height); - } else if (TabStripPlacement == Dock.Bottom) { - startPoint = new Point(0, offsetDelta); - endPoint = new Point(Bounds.Width, offsetDelta); - } else { - startPoint = new Point(Bounds.Width - offsetDelta, 0); - endPoint = new Point(Bounds.Width - offsetDelta, Bounds.Height); - } - using var optionState = context.PushRenderOptions(new RenderOptions() { EdgeMode = EdgeMode.Aliased }); - context.DrawLine(new Pen(BorderBrush, borderThickness), startPoint, endPoint); + context.DrawLine(new Pen(BorderBrush, borderThickness), _tabStripBorderStartPoint, _tabStripBorderEndPoint); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabControlTheme.cs b/src/AtomUI.Controls/TabControl/TabControlTheme.cs index 92f8a2c..8691d60 100644 --- a/src/AtomUI.Controls/TabControl/TabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabControlTheme.cs @@ -19,12 +19,13 @@ internal class TabControlTheme : BaseTabControlTheme { var tabScrollViewer = new TabControlScrollViewer() { - Name = TabsContainerPart + Name = TabsContainerPart, }; CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabControl = baseTabControl; + tabScrollViewer.RegisterInNameScope(scope); CreateTemplateParentBinding(tabScrollViewer, DockPanel.DockProperty, TabControl.TabStripPlacementProperty); CreateTemplateParentBinding(tabScrollViewer, TabControlScrollViewer.MarginProperty, TabControl.TabStripMarginProperty); From c4c877084a1b65b434ed554c61250a8c515a985f Mon Sep 17 00:00:00 2001 From: polarboy Date: Sat, 3 Aug 2024 23:16:33 +0800 Subject: [PATCH 23/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20CardTabControl=20?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E5=9F=BA=E7=A1=80=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成 CardTabControl 控件基础样式 --- .../ShowCase/TabControlShowCase.axaml | 19 ++ .../TabControl/BaseTabControl.cs | 4 +- .../TabControl/CardTabControl.cs | 279 +++++++++++++++++- .../TabControl/CardTabControlTheme.cs | 165 +++++++++++ .../TabControl/CardTabItemTheme.cs | 167 ++++++++++- 5 files changed, 629 insertions(+), 5 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index de3d05e..07a9267 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -21,7 +21,26 @@ + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + + + + diff --git a/src/AtomUI.Controls/TabControl/BaseTabControl.cs b/src/AtomUI.Controls/TabControl/BaseTabControl.cs index 31ecaad..169115f 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControl.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControl.cs @@ -74,7 +74,7 @@ public class BaseTabControl : AvaloniaTabControl #endregion private Border? _frameDecorator; - private TabControlScrollViewer? _tabsContainer; + private Control? _tabsContainer; private Point _tabStripBorderStartPoint; private Point _tabStripBorderEndPoint; @@ -87,7 +87,7 @@ public class BaseTabControl : AvaloniaTabControl { base.OnApplyTemplate(e); _frameDecorator = e.NameScope.Find(BaseTabControlTheme.FrameDecoratorPart); - _tabsContainer = e.NameScope.Find(BaseTabControlTheme.TabsContainerPart); + _tabsContainer = e.NameScope.Find(BaseTabControlTheme.TabsContainerPart); SetupBorderBinding(); HandlePlacementChanged(); } diff --git a/src/AtomUI.Controls/TabControl/CardTabControl.cs b/src/AtomUI.Controls/TabControl/CardTabControl.cs index 1f3b0a2..8a6180d 100644 --- a/src/AtomUI.Controls/TabControl/CardTabControl.cs +++ b/src/AtomUI.Controls/TabControl/CardTabControl.cs @@ -1,6 +1,281 @@ -namespace AtomUI.Controls; +using AtomUI.Data; +using AtomUI.Theme.Data; +using AtomUI.Theme.Styling; +using AtomUI.Utils; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Interactivity; +using Avalonia.Layout; -public class CardTabControl : BaseTabControl +namespace AtomUI.Controls; + +public class CardTabControl : BaseTabControl, IControlCustomStyle { + #region 公共属性实现 + public readonly static StyledProperty IsShowAddTabButtonProperty = + AvaloniaProperty.Register(nameof(IsShowAddTabButton), false); + public readonly static RoutedEvent AddTabRequestEvent = + RoutedEvent.Register( + nameof(AddTabRequest), + RoutingStrategies.Bubble); + + public bool IsShowAddTabButton + { + get => GetValue(IsShowAddTabButtonProperty); + set => SetValue(IsShowAddTabButtonProperty, value); + } + + public event EventHandler? AddTabRequest + { + add => AddHandler(AddTabRequestEvent, value); + remove => RemoveHandler(AddTabRequestEvent, value); + } + + #endregion + + #region 内部属性实现 + + internal static readonly StyledProperty CardBorderRadiusProperty = + AvaloniaProperty.Register(nameof(CardBorderRadius)); + + internal static readonly StyledProperty CardBorderThicknessProperty = + AvaloniaProperty.Register(nameof(CardBorderThickness)); + + internal static readonly DirectProperty CardBorderRadiusSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(CardBorderRadiusSize), + o => o.CardBorderRadiusSize, + (o, v) => o.CardBorderRadiusSize = v); + + internal static readonly DirectProperty CardSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(CardSize), + o => o.CardSize, + (o, v) => o.CardSize = v); + + internal CornerRadius CardBorderRadius + { + get => GetValue(CardBorderRadiusProperty); + set => SetValue(CardBorderRadiusProperty, value); + } + + internal Thickness CardBorderThickness + { + get => GetValue(CardBorderThicknessProperty); + set => SetValue(CardBorderThicknessProperty, value); + } + + private CornerRadius _cardBorderRadiusSize; + internal CornerRadius CardBorderRadiusSize + { + get => _cardBorderRadiusSize; + set => SetAndRaise(CardBorderRadiusSizeProperty, ref _cardBorderRadiusSize, value); + } + + private double _cardSize; + internal double CardSize + { + get => _cardSize; + set => SetAndRaise(CardSizeProperty, ref _cardSize, value); + } + + #endregion + + private IControlCustomStyle _customStyle; + private IconButton? _addTabButton; + private ItemsPresenter? _itemsPresenter; + private Grid? _tabsContainer; + private BaseTabScrollViewer? _tabScrollViewer; + + public CardTabControl() + { + _customStyle = this; + } + + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) + { + return new TabItem + { + Shape = TabSharp.Card + }; + } + + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is TabItem tabItem) { + tabItem.Shape = TabSharp.Card; + BindUtils.RelayBind(this, CardBorderRadiusProperty, tabItem, TabItem.CornerRadiusProperty); + BindUtils.RelayBind(this, CardBorderThicknessProperty, tabItem, TabItem.BorderThicknessProperty); + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + TokenResourceBinder.CreateTokenBinding(this, CardBorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template, + new RenderScaleAwareThicknessConfigure(this)); + TokenResourceBinder.CreateTokenBinding(this, CardSizeProperty, TabControlResourceKey.CardSize); + _customStyle.HandleTemplateApplied(e.NameScope); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change.Property == SizeTypeProperty) { + HandleSizeTypeChanged(); + } else if (change.Property == TabStripPlacementProperty) { + SetupCardTabStripContainer(); + HandleTabStripPlacementChanged(); + } + } + + private void SetupCardTabStripContainer(Size finalSize) + { + if (_tabsContainer is not null) { + double addButtonOffset = 0; + double markOffset = 0; + if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + addButtonOffset = _addTabButton?.Bounds.Right ?? 0; + markOffset = finalSize.Width; + if (addButtonOffset > markOffset) { + _tabsContainer.ColumnDefinitions[0].Width = GridLength.Star; + } else { + _tabsContainer.ColumnDefinitions[0].Width = GridLength.Auto; + } + } else { + addButtonOffset = _addTabButton?.Bounds.Bottom ?? 0; + markOffset = finalSize.Height; + if (addButtonOffset > markOffset) { + _tabsContainer.RowDefinitions[0].Height = GridLength.Star; + } else { + _tabsContainer.RowDefinitions[0].Height = GridLength.Auto; + } + } + + } + } + + #region IControlCustomStyle 实现 + + void IControlCustomStyle.HandleTemplateApplied(INameScope scope) + { + _addTabButton = scope.Find(CardTabControlTheme.AddTabButtonPart); + _itemsPresenter = scope.Find(CardTabControlTheme.ItemsPresenterPart); + _tabsContainer = scope.Find(CardTabControlTheme.TabsContainerPart); + _tabScrollViewer = scope.Find(CardTabControlTheme.CardTabStripScrollViewerPart); + if (_addTabButton is not null) { + _addTabButton.Click += HandleAddButtonClicked; + } + HandleSizeTypeChanged(); + SetupCardTabStripContainer(); + } + #endregion + + private void HandleAddButtonClicked(object? sender, RoutedEventArgs args) + { + RaiseEvent(new RoutedEventArgs(AddTabRequestEvent)); + } + + private void HandleSizeTypeChanged() + { + if (SizeType == SizeType.Large) { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusLG); + } else if (SizeType == SizeType.Middle) { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadius); + } else { + TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusSM); + } + } + + protected override Size ArrangeOverride(Size finalSize) + { + var size = base.ArrangeOverride(finalSize); + SetupCardTabStripContainer(finalSize); + HandleTabStripPlacementChanged(); + return size; + } + + private void SetupCardTabStripContainer() + { + if (TabStripPlacement == Dock.Top || + TabStripPlacement == Dock.Bottom) { + if (_tabsContainer is not null) { + _tabsContainer.Children.Clear(); + _tabsContainer.RowDefinitions.Clear(); + _tabsContainer.ColumnDefinitions = new ColumnDefinitions() + { + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto), + }; + } + + if (_tabScrollViewer is not null) { + Grid.SetColumn(_tabScrollViewer, 0); + } + + if (_addTabButton is not null) { + Grid.SetColumn(_addTabButton, 1); + } + + _tabsContainer!.Children.Add(_tabScrollViewer!); + _tabsContainer.Children.Add(_addTabButton!); + } else { + if (_tabsContainer is not null) { + _tabsContainer.Children.Clear(); + _tabsContainer.ColumnDefinitions.Clear(); + _tabsContainer.RowDefinitions = new RowDefinitions() + { + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + }; + } + if (_tabScrollViewer is not null) { + Grid.SetRow(_tabScrollViewer, 0); + } + + if (_addTabButton is not null) { + Grid.SetRow(_addTabButton, 1); + } + _tabsContainer!.Children.Add(_tabScrollViewer!); + _tabsContainer.Children.Add(_addTabButton!); + } + } + + private void HandleTabStripPlacementChanged() + { + if (TabStripPlacement == Dock.Top) { + CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, + topRight: _cardBorderRadiusSize.TopRight, bottomLeft: 0, bottomRight: 0); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _cardSize; + _addTabButton.Height = _itemsPresenter.DesiredSize.Height; + } + } else if (TabStripPlacement == Dock.Bottom) { + CardBorderRadius = new CornerRadius(topLeft: 0, 0, bottomLeft: _cardBorderRadiusSize.BottomLeft, + bottomRight: _cardBorderRadiusSize.BottomRight); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _cardSize; + _addTabButton.Height = _itemsPresenter.DesiredSize.Height; + } + } else if (TabStripPlacement == Dock.Left) { + CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0, + bottomLeft: _cardBorderRadiusSize.BottomLeft, bottomRight: 0); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _cardSize; + _addTabButton.Height = _cardSize; + _addTabButton.HorizontalAlignment = HorizontalAlignment.Right; + } + } else { + CardBorderRadius = new CornerRadius(topLeft: 0, topRight: _cardBorderRadiusSize.TopRight, bottomLeft: 0, + bottomRight: _cardBorderRadiusSize.BottomRight); + if (_addTabButton is not null && _itemsPresenter is not null) { + _addTabButton.Width = _cardSize; + _addTabButton.Height = _cardSize; + _addTabButton.HorizontalAlignment = HorizontalAlignment.Left; + } + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs b/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs index e0e3cae..c66bb27 100644 --- a/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs @@ -1,9 +1,174 @@ using AtomUI.Theme.Styling; +using AtomUI.Utils; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Layout; +using Avalonia.Styling; namespace AtomUI.Controls; [ControlThemeProvider] internal class CardTabControlTheme : BaseTabControlTheme { + public const string AddTabButtonPart = "Part_AddTabButton"; + public const string CardTabStripScrollViewerPart = "Part_CardTabStripScrollViewer"; + public CardTabControlTheme() : base(typeof(CardTabControl)) { } + + protected override void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container) + { + var cardTabStripContainer = new Grid() + { + Name = TabsContainerPart, + }; + cardTabStripContainer.RegisterInNameScope(scope); + + CreateTemplateParentBinding(cardTabStripContainer, DockPanel.DockProperty, BaseTabControl.TabStripPlacementProperty); + CreateTemplateParentBinding(cardTabStripContainer, Grid.MarginProperty, BaseTabControl.TabStripMarginProperty); + + TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, + TabControlResourceKey.CardGutter); + + var tabScrollViewer = new TabControlScrollViewer() + { + Name = CardTabStripScrollViewerPart + }; + tabScrollViewer.RegisterInNameScope(scope); + CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabControl.TabStripPlacementProperty); + var contentPanel = CreateTabStripContentPanel(scope); + tabScrollViewer.Content = contentPanel; + tabScrollViewer.TabControl = baseTabControl; + + var addTabIcon = new PathIcon() + { + Kind = "PlusOutlined" + }; + + TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor); + TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor); + TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled); + + TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSize); + TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSize); + + var addTabButton = new IconButton + { + Name = AddTabButtonPart, + BorderThickness = new Thickness(1), + Icon = addTabIcon + }; + + CreateTemplateParentBinding(addTabButton, IconButton.BorderThicknessProperty, CardTabControl.CardBorderThicknessProperty); + CreateTemplateParentBinding(addTabButton, IconButton.CornerRadiusProperty, CardTabControl.CardBorderRadiusProperty); + CreateTemplateParentBinding(addTabButton, IconButton.IsVisibleProperty, CardTabControl.IsShowAddTabButtonProperty); + + TokenResourceBinder.CreateGlobalResourceBinding(addTabButton, IconButton.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + + addTabButton.RegisterInNameScope(scope); + + cardTabStripContainer.Children.Add(tabScrollViewer); + cardTabStripContainer.Children.Add(addTabButton); + + container.Children.Add(cardTabStripContainer); + } + + private ItemsPresenter CreateTabStripContentPanel(INameScope scope) + { + var itemsPresenter = new ItemsPresenter + { + Name = ItemsPresenterPart, + }; + itemsPresenter.RegisterInNameScope(scope); + + CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, TabControl.ItemsPanelProperty); + return itemsPresenter; + } + + protected override void BuildStyles() + { + base.BuildStyles(); + var commonStyle = new Style(selector => selector.Nesting()); + + // 设置 items presenter 面板样式 + // 分为上、右、下、左 + { + // 上 + var topStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.TopPC)); + var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + topStyle.Add(containerStyle); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal); + topStyle.Add(addTabButtonStyle); + + topStyle.Add(itemPresenterPanelStyle); + commonStyle.Add(topStyle); + } + + { + // 右 + var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.RightPC)); + + var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + rightStyle.Add(containerStyle); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + rightStyle.Add(itemPresenterPanelStyle); + + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical); + rightStyle.Add(addTabButtonStyle); + + commonStyle.Add(rightStyle); + } + { + // 下 + var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.BottomPC)); + + var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + containerStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + bottomStyle.Add(containerStyle); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + bottomStyle.Add(itemPresenterPanelStyle); + + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal); + bottomStyle.Add(addTabButtonStyle); + + commonStyle.Add(bottomStyle); + } + { + // 左 + var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.LeftPC)); + + var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + leftStyle.Add(containerStyle); + + var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType()); + itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical); + itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); + leftStyle.Add(itemPresenterPanelStyle); + + var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart)); + addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical); + leftStyle.Add(addTabButtonStyle); + + commonStyle.Add(leftStyle); + } + + Add(commonStyle); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/CardTabItemTheme.cs b/src/AtomUI.Controls/TabControl/CardTabItemTheme.cs index 8fdd584..05444e7 100644 --- a/src/AtomUI.Controls/TabControl/CardTabItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabItemTheme.cs @@ -1,4 +1,10 @@ -using AtomUI.Theme.Styling; +using AtomUI.Media; +using AtomUI.Theme.Styling; +using AtomUI.Theme.Utils; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Styling; namespace AtomUI.Controls; @@ -13,4 +19,163 @@ internal class CardTabItemTheme : BaseTabItemTheme { return ID; } + + protected override void NotifyBuildControlTemplate(TabItem tabItem, INameScope scope, Border container) + { + base.NotifyBuildControlTemplate(tabItem, scope, container); + + if (container.Transitions is null) { + var transitions = new Transitions(); + transitions.Add(AnimationUtils.CreateTransition(Border.BackgroundProperty)); + container.Transitions = transitions; + } + CreateTemplateParentBinding(container, Border.BorderThicknessProperty, TabItem.BorderThicknessProperty); + CreateTemplateParentBinding(container, Border.CornerRadiusProperty, TabItem.CornerRadiusProperty); + } + + protected override void BuildStyles() + { + base.BuildStyles(); + + var commonStyle = new Style(selector => selector.Nesting()); + + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin); + decoratorStyle.Add(Border.BackgroundProperty, TabControlResourceKey.CardBg); + decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + commonStyle.Add(decoratorStyle); + } + + // 选中 + var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainer); + selectedStyle.Add(decoratorStyle); + } + commonStyle.Add(selectedStyle); + + Add(commonStyle); + + BuildSizeTypeStyle(); + BuildPlacementStyle(); + BuildDisabledStyle(); + } + + protected void BuildSizeTypeStyle() + { + var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Top), + selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Bottom))); + { + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingLG); + largeSizeStyle.Add(decoratorStyle); + } + topOrBottomStyle.Add(largeSizeStyle); + + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPadding); + middleSizeStyle.Add(decoratorStyle); + } + topOrBottomStyle.Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingSM); + smallSizeType.Add(decoratorStyle); + } + + topOrBottomStyle.Add(smallSizeType); + } + Add(topOrBottomStyle); + + var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Left), + selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Right))); + { + var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + largeSizeStyle.Add(decoratorStyle); + } + leftOrRightStyle.Add(largeSizeStyle); + + var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + middleSizeStyle.Add(decoratorStyle); + } + leftOrRightStyle.Add(middleSizeStyle); + + var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small)); + { + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding); + smallSizeType.Add(decoratorStyle); + } + + leftOrRightStyle.Add(smallSizeType); + } + Add(leftOrRightStyle); + } + + private void BuildPlacementStyle() + { + // 设置 items presenter 面板样式 + // 分为上、右、下、左 + { + // 上 + var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Top)); + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center); + topStyle.Add(iconStyle); + + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); + + Add(topStyle); + } + + { + // 右 + var rightStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.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(TabItem.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(TabItem.TabStripPlacementProperty, Dock.Left)); + var iconStyle = new Style(selector => selector.Nesting().Template().OfType()); + iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center); + leftStyle.Add(iconStyle); + Add(leftStyle); + } + } + + private void BuildDisabledStyle() + { + var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled)); + var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart)); + decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainerDisabled); + disabledStyle.Add(decoratorStyle); + Add(disabledStyle); + } } \ No newline at end of file From db16644ac5f11676c954971af94f641028ca5201 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sun, 4 Aug 2024 11:07:19 +0800 Subject: [PATCH 24/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20CardTabControl=20?= =?UTF-8?q?=E5=B1=85=E4=B8=AD=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 CardTabControl 居中显示问题,在整体的 ScrollViewer 之上再增加用于居中对其的 Panel --- .../ShowCase/TabControlShowCase.axaml | 20 +++ .../ArrowDecoratedBoxTheme.cs | 2 +- .../Loading/LoadingIndicatorAdornerTheme.cs | 4 +- .../Loading/LoadingIndicatorTheme.cs | 4 +- src/AtomUI.Controls/Menu/MenuItemTheme.cs | 14 +- .../Menu/MenuScrollViewerTheme.cs | 6 +- .../TabControl/BaseTabControl.cs | 10 +- .../TabControl/BaseTabControlTheme.cs | 33 ++-- .../TabControl/BaseTabItemTheme.cs | 4 +- .../TabControl/BaseTabScrollViewerTheme.cs | 6 +- .../TabControl/CardTabControl.cs | 148 +++++++++--------- .../TabControl/CardTabControlTheme.cs | 27 ++-- .../TabControl/OverflowTabMenuItemTheme.cs | 8 +- .../TabControl/TabControlTheme.cs | 16 +- .../TabStrip/BaseTabStripItemTheme.cs | 4 +- .../TabControl/TabStrip/BaseTabStripTheme.cs | 2 +- .../TabControl/TabStrip/CardTabStripTheme.cs | 6 +- 17 files changed, 175 insertions(+), 139 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 07a9267..0fc880d 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -39,6 +39,26 @@ + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + + + + + diff --git a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs index 88f40fa..180ae7c 100644 --- a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs +++ b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs @@ -11,7 +11,7 @@ namespace AtomUI.Controls; [ControlThemeProvider] public class ArrowDecoratedBoxTheme : BaseControlTheme { - public const string DecoratorPart = "Part_Decorator"; + public const string DecoratorPart = "PART_Decorator"; public ArrowDecoratedBoxTheme() : base(typeof(ArrowDecoratedBox)) {} diff --git a/src/AtomUI.Controls/Loading/LoadingIndicatorAdornerTheme.cs b/src/AtomUI.Controls/Loading/LoadingIndicatorAdornerTheme.cs index cd2b97b..a4c32a1 100644 --- a/src/AtomUI.Controls/Loading/LoadingIndicatorAdornerTheme.cs +++ b/src/AtomUI.Controls/Loading/LoadingIndicatorAdornerTheme.cs @@ -8,8 +8,8 @@ namespace AtomUI.Controls; [ControlThemeProvider] public class LoadingIndicatorAdornerTheme : BaseControlTheme { - public const string LoadingIndicatorPart = "Part_LoadingIndicator"; - public const string MainContainerPart = "Part_MainContainer"; + public const string LoadingIndicatorPart = "PART_LoadingIndicator"; + public const string MainContainerPart = "PART_MainContainer"; public LoadingIndicatorAdornerTheme() : base(typeof(LoadingIndicatorAdorner)) diff --git a/src/AtomUI.Controls/Loading/LoadingIndicatorTheme.cs b/src/AtomUI.Controls/Loading/LoadingIndicatorTheme.cs index 88a3a14..c5e8a66 100644 --- a/src/AtomUI.Controls/Loading/LoadingIndicatorTheme.cs +++ b/src/AtomUI.Controls/Loading/LoadingIndicatorTheme.cs @@ -12,8 +12,8 @@ namespace AtomUI.Controls; [ControlThemeProvider] public class LoadingIndicatorTheme : BaseControlTheme { - public const string MainContainerPart = "Part_MainContainer"; - public const string LoadingTextPart = "Part_LoadingText"; + public const string MainContainerPart = "PART_MainContainer"; + public const string LoadingTextPart = "PART_LoadingText"; public LoadingIndicatorTheme() : base(typeof(LoadingIndicator)) diff --git a/src/AtomUI.Controls/Menu/MenuItemTheme.cs b/src/AtomUI.Controls/Menu/MenuItemTheme.cs index 2cb213a..030ab22 100644 --- a/src/AtomUI.Controls/Menu/MenuItemTheme.cs +++ b/src/AtomUI.Controls/Menu/MenuItemTheme.cs @@ -17,13 +17,13 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class MenuItemTheme : BaseControlTheme { - public const string ItemDecoratorPart = "Part_ItemDecorator"; - public const string MainContainerPart = "Part_MainContainer"; - public const string TogglePresenterPart = "Part_TogglePresenter"; - public const string ItemIconPresenterPart = "Part_ItemIconPresenter"; - public const string ItemTextPresenterPart = "Part_ItemTextPresenter"; - public const string InputGestureTextPart = "Part_InputGestureText"; - public const string MenuIndicatorIconPart = "Part_MenuIndicatorIcon"; + public const string ItemDecoratorPart = "PART_ItemDecorator"; + public const string MainContainerPart = "PART_MainContainer"; + public const string TogglePresenterPart = "PART_TogglePresenter"; + public const string ItemIconPresenterPart = "PART_ItemIconPresenter"; + public const string ItemTextPresenterPart = "PART_ItemTextPresenter"; + public const string InputGestureTextPart = "PART_InputGestureText"; + public const string MenuIndicatorIconPart = "PART_MenuIndicatorIcon"; public const string PopupPart = "PART_Popup"; public const string ItemsPresenterPart = "PART_ItemsPresenter"; diff --git a/src/AtomUI.Controls/Menu/MenuScrollViewerTheme.cs b/src/AtomUI.Controls/Menu/MenuScrollViewerTheme.cs index 299534f..e779ac2 100644 --- a/src/AtomUI.Controls/Menu/MenuScrollViewerTheme.cs +++ b/src/AtomUI.Controls/Menu/MenuScrollViewerTheme.cs @@ -17,10 +17,10 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class MenuScrollViewerTheme : BaseControlTheme { - public const string ScrollUpButtonPart = "Part_ScrollUpButton"; - public const string ScrollDownButtonPart = "Part_ScrollDownButton"; + public const string ScrollUpButtonPart = "PART_ScrollUpButton"; + public const string ScrollDownButtonPart = "PART_ScrollDownButton"; public const string ScrollViewContentPart = "PART_ContentPresenter"; - public const string MainContainerPart = "Part_MainContainer"; + public const string MainContainerPart = "PART_MainContainer"; public MenuScrollViewerTheme() : base(typeof(MenuScrollViewer)) { } diff --git a/src/AtomUI.Controls/TabControl/BaseTabControl.cs b/src/AtomUI.Controls/TabControl/BaseTabControl.cs index 169115f..47a0756 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControl.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControl.cs @@ -74,7 +74,7 @@ public class BaseTabControl : AvaloniaTabControl #endregion private Border? _frameDecorator; - private Control? _tabsContainer; + private Panel? _alignWrapper; private Point _tabStripBorderStartPoint; private Point _tabStripBorderEndPoint; @@ -87,7 +87,7 @@ public class BaseTabControl : AvaloniaTabControl { base.OnApplyTemplate(e); _frameDecorator = e.NameScope.Find(BaseTabControlTheme.FrameDecoratorPart); - _tabsContainer = e.NameScope.Find(BaseTabControlTheme.TabsContainerPart); + _alignWrapper = e.NameScope.Find(BaseTabControlTheme.AlignWrapperPart); SetupBorderBinding(); HandlePlacementChanged(); } @@ -148,9 +148,9 @@ public class BaseTabControl : AvaloniaTabControl private void SetupTabStripBorderPoints() { - if (_tabsContainer is not null) { - var offset = _tabsContainer.TranslatePoint(new Point(0, 0), this) ?? default; - var size = _tabsContainer.Bounds.Size; + if (_alignWrapper is not null) { + var offset = _alignWrapper.TranslatePoint(new Point(0, 0), this) ?? default; + var size = _alignWrapper.Bounds.Size; var borderThickness = BorderThickness.Left; var offsetDelta = borderThickness / 2; if (TabStripPlacement == Dock.Top) { diff --git a/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs index 21505e1..4f80990 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs @@ -15,6 +15,7 @@ internal class BaseTabControlTheme : BaseControlTheme public const string MainLayoutContainerPart = "PART_MainLayoutContainer"; public const string SelectedContentHostPart = "PART_SelectedContentHost"; public const string TabsContainerPart = "PART_TabsContainer"; + public const string AlignWrapperPart = "PART_AlignWrapper"; public BaseTabControlTheme(Type targetType) : base(targetType) { } @@ -73,11 +74,11 @@ internal class BaseTabControlTheme : BaseControlTheme topStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Top); // tabs 是否居中 - var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); + var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true)); { - var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart)); - itemsPresenterStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); - tabAlignCenterStyle.Add(itemsPresenterStyle); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } topStyle.Add(tabAlignCenterStyle); @@ -92,11 +93,11 @@ internal class BaseTabControlTheme : BaseControlTheme rightStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Stretch); // tabs 是否居中 - var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); + var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true)); { - var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart)); - itemsPresenterStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center); - tabAlignCenterStyle.Add(itemsPresenterStyle); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } rightStyle.Add(tabAlignCenterStyle); @@ -109,11 +110,11 @@ internal class BaseTabControlTheme : BaseControlTheme bottomStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Top); // tabs 是否居中 - var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); + var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true)); { - var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart)); - itemsPresenterStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); - tabAlignCenterStyle.Add(itemsPresenterStyle); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } bottomStyle.Add(tabAlignCenterStyle); @@ -128,11 +129,11 @@ internal class BaseTabControlTheme : BaseControlTheme leftStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Stretch); // tabs 是否居中 - var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true)); + var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true)); { - var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart)); - itemsPresenterStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center); - tabAlignCenterStyle.Add(itemsPresenterStyle); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } leftStyle.Add(tabAlignCenterStyle); diff --git a/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs index 3d3753a..867375e 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs @@ -13,8 +13,8 @@ namespace AtomUI.Controls; internal class BaseTabItemTheme : BaseControlTheme { - public const string DecoratorPart = "Part_Decorator"; - public const string ContentLayoutPart = "Part_ContentLayout"; + 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 const string ItemCloseButtonPart = "PART_ItemCloseButton"; diff --git a/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs index 89ec163..d3b283c 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs @@ -15,9 +15,9 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class BaseTabScrollViewerTheme : BaseControlTheme { - public const string ScrollStartEdgeIndicatorPart = "Part_ScrollStartEdgeIndicator"; - public const string ScrollEndEdgeIndicatorPart = "Part_ScrollEndEdgeIndicator"; - public const string ScrollMenuIndicatorPart = "Part_ScrollMenuIndicator"; + public const string ScrollStartEdgeIndicatorPart = "PART_ScrollStartEdgeIndicator"; + public const string ScrollEndEdgeIndicatorPart = "PART_ScrollEndEdgeIndicator"; + public const string ScrollMenuIndicatorPart = "PART_ScrollMenuIndicator"; public const string ScrollViewContentPart = "PART_ContentPresenter"; public const string ScrollViewLayoutPart = "PART_ScrollViewLayout"; public const string ScrollViewWrapperLayoutPart = "PART_ScrollViewWrapperLayout"; diff --git a/src/AtomUI.Controls/TabControl/CardTabControl.cs b/src/AtomUI.Controls/TabControl/CardTabControl.cs index 8a6180d..7e9fbe8 100644 --- a/src/AtomUI.Controls/TabControl/CardTabControl.cs +++ b/src/AtomUI.Controls/TabControl/CardTabControl.cs @@ -127,36 +127,36 @@ public class CardTabControl : BaseTabControl, IControlCustomStyle if (change.Property == SizeTypeProperty) { HandleSizeTypeChanged(); } else if (change.Property == TabStripPlacementProperty) { - SetupCardTabStripContainer(); + // SetupCardTabStripContainer(); HandleTabStripPlacementChanged(); } } - private void SetupCardTabStripContainer(Size finalSize) - { - if (_tabsContainer is not null) { - double addButtonOffset = 0; - double markOffset = 0; - if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { - addButtonOffset = _addTabButton?.Bounds.Right ?? 0; - markOffset = finalSize.Width; - if (addButtonOffset > markOffset) { - _tabsContainer.ColumnDefinitions[0].Width = GridLength.Star; - } else { - _tabsContainer.ColumnDefinitions[0].Width = GridLength.Auto; - } - } else { - addButtonOffset = _addTabButton?.Bounds.Bottom ?? 0; - markOffset = finalSize.Height; - if (addButtonOffset > markOffset) { - _tabsContainer.RowDefinitions[0].Height = GridLength.Star; - } else { - _tabsContainer.RowDefinitions[0].Height = GridLength.Auto; - } - } - - } - } + // private void SetupCardTabStripContainer(Size finalSize) + // { + // if (_tabsContainer is not null) { + // double addButtonOffset = 0; + // double markOffset = 0; + // if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + // addButtonOffset = _addTabButton?.Bounds.Right ?? 0; + // markOffset = finalSize.Width; + // if (addButtonOffset > markOffset) { + // _tabsContainer.ColumnDefinitions[0].Width = GridLength.Star; + // } else { + // _tabsContainer.ColumnDefinitions[0].Width = GridLength.Auto; + // } + // } else { + // addButtonOffset = _addTabButton?.Bounds.Bottom ?? 0; + // markOffset = finalSize.Height; + // if (addButtonOffset > markOffset) { + // _tabsContainer.RowDefinitions[0].Height = GridLength.Star; + // } else { + // _tabsContainer.RowDefinitions[0].Height = GridLength.Auto; + // } + // } + // + // } + // } #region IControlCustomStyle 实现 @@ -164,13 +164,13 @@ public class CardTabControl : BaseTabControl, IControlCustomStyle { _addTabButton = scope.Find(CardTabControlTheme.AddTabButtonPart); _itemsPresenter = scope.Find(CardTabControlTheme.ItemsPresenterPart); - _tabsContainer = scope.Find(CardTabControlTheme.TabsContainerPart); + // _tabsContainer = scope.Find(CardTabControlTheme.TabsContainerPart); _tabScrollViewer = scope.Find(CardTabControlTheme.CardTabStripScrollViewerPart); if (_addTabButton is not null) { _addTabButton.Click += HandleAddButtonClicked; } HandleSizeTypeChanged(); - SetupCardTabStripContainer(); + // SetupCardTabStripContainer(); } #endregion @@ -193,56 +193,56 @@ public class CardTabControl : BaseTabControl, IControlCustomStyle protected override Size ArrangeOverride(Size finalSize) { var size = base.ArrangeOverride(finalSize); - SetupCardTabStripContainer(finalSize); + // SetupCardTabStripContainer(finalSize); HandleTabStripPlacementChanged(); return size; } - private void SetupCardTabStripContainer() - { - if (TabStripPlacement == Dock.Top || - TabStripPlacement == Dock.Bottom) { - if (_tabsContainer is not null) { - _tabsContainer.Children.Clear(); - _tabsContainer.RowDefinitions.Clear(); - _tabsContainer.ColumnDefinitions = new ColumnDefinitions() - { - new ColumnDefinition(GridLength.Auto), - new ColumnDefinition(GridLength.Auto), - }; - } - - if (_tabScrollViewer is not null) { - Grid.SetColumn(_tabScrollViewer, 0); - } - - if (_addTabButton is not null) { - Grid.SetColumn(_addTabButton, 1); - } - - _tabsContainer!.Children.Add(_tabScrollViewer!); - _tabsContainer.Children.Add(_addTabButton!); - } else { - if (_tabsContainer is not null) { - _tabsContainer.Children.Clear(); - _tabsContainer.ColumnDefinitions.Clear(); - _tabsContainer.RowDefinitions = new RowDefinitions() - { - new RowDefinition(GridLength.Auto), - new RowDefinition(GridLength.Auto), - }; - } - if (_tabScrollViewer is not null) { - Grid.SetRow(_tabScrollViewer, 0); - } - - if (_addTabButton is not null) { - Grid.SetRow(_addTabButton, 1); - } - _tabsContainer!.Children.Add(_tabScrollViewer!); - _tabsContainer.Children.Add(_addTabButton!); - } - } + // private void SetupCardTabStripContainer() + // { + // if (TabStripPlacement == Dock.Top || + // TabStripPlacement == Dock.Bottom) { + // if (_tabsContainer is not null) { + // _tabsContainer.Children.Clear(); + // _tabsContainer.RowDefinitions.Clear(); + // _tabsContainer.ColumnDefinitions = new ColumnDefinitions() + // { + // new ColumnDefinition(GridLength.Auto), + // new ColumnDefinition(GridLength.Auto), + // }; + // } + // + // if (_tabScrollViewer is not null) { + // Grid.SetColumn(_tabScrollViewer, 0); + // } + // + // if (_addTabButton is not null) { + // Grid.SetColumn(_addTabButton, 1); + // } + // + // _tabsContainer!.Children.Add(_tabScrollViewer!); + // _tabsContainer.Children.Add(_addTabButton!); + // } else { + // if (_tabsContainer is not null) { + // _tabsContainer.Children.Clear(); + // _tabsContainer.ColumnDefinitions.Clear(); + // _tabsContainer.RowDefinitions = new RowDefinitions() + // { + // new RowDefinition(GridLength.Auto), + // new RowDefinition(GridLength.Auto), + // }; + // } + // if (_tabScrollViewer is not null) { + // Grid.SetRow(_tabScrollViewer, 0); + // } + // + // if (_addTabButton is not null) { + // Grid.SetRow(_addTabButton, 1); + // } + // _tabsContainer!.Children.Add(_tabScrollViewer!); + // _tabsContainer.Children.Add(_addTabButton!); + // } + // } private void HandleTabStripPlacementChanged() { diff --git a/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs b/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs index c66bb27..e80e976 100644 --- a/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs @@ -12,21 +12,26 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class CardTabControlTheme : BaseTabControlTheme { - public const string AddTabButtonPart = "Part_AddTabButton"; - public const string CardTabStripScrollViewerPart = "Part_CardTabStripScrollViewer"; + public const string AddTabButtonPart = "PART_AddTabButton"; + public const string CardTabStripScrollViewerPart = "PART_CardTabStripScrollViewer"; public CardTabControlTheme() : base(typeof(CardTabControl)) { } protected override void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container) - { - var cardTabStripContainer = new Grid() + { + var alignWrapper = new Panel() + { + Name = AlignWrapperPart + }; + alignWrapper.RegisterInNameScope(scope); + CreateTemplateParentBinding(alignWrapper, DockPanel.DockProperty, BaseTabControl.TabStripPlacementProperty); + CreateTemplateParentBinding(alignWrapper, Panel.MarginProperty,TabControl.TabStripMarginProperty); + + var cardTabStripContainer = new DockPanel() { Name = TabsContainerPart, }; cardTabStripContainer.RegisterInNameScope(scope); - - CreateTemplateParentBinding(cardTabStripContainer, DockPanel.DockProperty, BaseTabControl.TabStripPlacementProperty); - CreateTemplateParentBinding(cardTabStripContainer, Grid.MarginProperty, BaseTabControl.TabStripMarginProperty); TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, TabControlResourceKey.CardGutter); @@ -35,8 +40,8 @@ internal class CardTabControlTheme : BaseTabControlTheme { Name = CardTabStripScrollViewerPart }; + CreateTemplateParentBinding(tabScrollViewer, TabControlScrollViewer.TabStripPlacementProperty, BaseTabControl.TabStripPlacementProperty); tabScrollViewer.RegisterInNameScope(scope); - CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabControl.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabControl = baseTabControl; @@ -68,10 +73,12 @@ internal class CardTabControlTheme : BaseTabControlTheme addTabButton.RegisterInNameScope(scope); - cardTabStripContainer.Children.Add(tabScrollViewer); cardTabStripContainer.Children.Add(addTabButton); + cardTabStripContainer.Children.Add(tabScrollViewer); - container.Children.Add(cardTabStripContainer); + alignWrapper.Children.Add(cardTabStripContainer); + + container.Children.Add(alignWrapper); } private ItemsPresenter CreateTabStripContentPanel(INameScope scope) diff --git a/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs b/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs index e49bedc..76392a3 100644 --- a/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs @@ -15,10 +15,10 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class OverflowTabMenuItemTheme : BaseControlTheme { - public const string ItemDecoratorPart = "Part_ItemDecorator"; - public const string MainContainerPart = "Part_MainContainer"; - public const string ItemTextPresenterPart = "Part_ItemTextPresenter"; - public const string ItemCloseButtonPart = "Part_ItemCloseIcon"; + public const string ItemDecoratorPart = "PART_ItemDecorator"; + public const string MainContainerPart = "PART_MainContainer"; + public const string ItemTextPresenterPart = "PART_ItemTextPresenter"; + public const string ItemCloseButtonPart = "PART_ItemCloseIcon"; public OverflowTabMenuItemTheme() : base(typeof(OverflowTabMenuItem)) diff --git a/src/AtomUI.Controls/TabControl/TabControlTheme.cs b/src/AtomUI.Controls/TabControl/TabControlTheme.cs index 8691d60..ad89b3e 100644 --- a/src/AtomUI.Controls/TabControl/TabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabControlTheme.cs @@ -17,19 +17,27 @@ internal class TabControlTheme : BaseTabControlTheme protected override void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container) { + var alignWrapper = new Panel() + { + Name = AlignWrapperPart + }; + alignWrapper.RegisterInNameScope(scope); + CreateTemplateParentBinding(alignWrapper, DockPanel.DockProperty, TabControl.TabStripPlacementProperty); + CreateTemplateParentBinding(alignWrapper, Panel.MarginProperty,TabControl.TabStripMarginProperty); + var tabScrollViewer = new TabControlScrollViewer() { Name = TabsContainerPart, }; CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); + var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabControl = baseTabControl; tabScrollViewer.RegisterInNameScope(scope); - CreateTemplateParentBinding(tabScrollViewer, DockPanel.DockProperty, TabControl.TabStripPlacementProperty); - CreateTemplateParentBinding(tabScrollViewer, TabControlScrollViewer.MarginProperty, - TabControl.TabStripMarginProperty); - container.Children.Add(tabScrollViewer); + + alignWrapper.Children.Add(tabScrollViewer); + container.Children.Add(alignWrapper); } private Panel CreateTabStripContentPanel(INameScope scope) diff --git a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs index 199771d..c2deb02 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripItemTheme.cs @@ -13,8 +13,8 @@ namespace AtomUI.Controls; internal class BaseTabStripItemTheme : BaseControlTheme { - public const string DecoratorPart = "Part_Decorator"; - public const string ContentLayoutPart = "Part_ContentLayout"; + 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 const string ItemCloseButtonPart = "PART_ItemCloseButton"; diff --git a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs index 044ad49..0e54da5 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs @@ -10,7 +10,7 @@ namespace AtomUI.Controls; internal class BaseTabStripTheme : BaseControlTheme { - public const string FrameDecoratorPart = "Part_FrameDecorator"; + public const string FrameDecoratorPart = "PART_FrameDecorator"; public const string ItemsPresenterPart = "PART_ItemsPresenter"; public BaseTabStripTheme(Type targetType) : base(targetType) { } diff --git a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs index 7d66141..73dd0ba 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs @@ -12,9 +12,9 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class CardTabStripTheme : BaseTabStripTheme { - public const string AddTabButtonPart = "Part_AddTabButton"; - public const string CardTabStripContainerPart = "Part_CardTabStripContainer"; - public const string CardTabStripScrollViewerPart = "Part_CardTabStripScrollViewer"; + public const string AddTabButtonPart = "PART_AddTabButton"; + public const string CardTabStripContainerPart = "PART_CardTabStripContainer"; + public const string CardTabStripScrollViewerPart = "PART_CardTabStripScrollViewer"; public CardTabStripTheme() : base(typeof(CardTabStrip)) { } From c92a0f557f22054d325267a21a35dfedc9ea9599 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sun, 4 Aug 2024 11:10:36 +0800 Subject: [PATCH 25/28] =?UTF-8?q?=E5=88=A0=E9=99=A4=20CardTabControl=20?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除 CardTabControl 无用代码 --- .../TabControl/CardTabControl.cs | 79 ------------------- 1 file changed, 79 deletions(-) diff --git a/src/AtomUI.Controls/TabControl/CardTabControl.cs b/src/AtomUI.Controls/TabControl/CardTabControl.cs index 7e9fbe8..5bad5bf 100644 --- a/src/AtomUI.Controls/TabControl/CardTabControl.cs +++ b/src/AtomUI.Controls/TabControl/CardTabControl.cs @@ -86,8 +86,6 @@ public class CardTabControl : BaseTabControl, IControlCustomStyle private IControlCustomStyle _customStyle; private IconButton? _addTabButton; private ItemsPresenter? _itemsPresenter; - private Grid? _tabsContainer; - private BaseTabScrollViewer? _tabScrollViewer; public CardTabControl() { @@ -127,50 +125,20 @@ public class CardTabControl : BaseTabControl, IControlCustomStyle if (change.Property == SizeTypeProperty) { HandleSizeTypeChanged(); } else if (change.Property == TabStripPlacementProperty) { - // SetupCardTabStripContainer(); HandleTabStripPlacementChanged(); } } - // private void SetupCardTabStripContainer(Size finalSize) - // { - // if (_tabsContainer is not null) { - // double addButtonOffset = 0; - // double markOffset = 0; - // if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { - // addButtonOffset = _addTabButton?.Bounds.Right ?? 0; - // markOffset = finalSize.Width; - // if (addButtonOffset > markOffset) { - // _tabsContainer.ColumnDefinitions[0].Width = GridLength.Star; - // } else { - // _tabsContainer.ColumnDefinitions[0].Width = GridLength.Auto; - // } - // } else { - // addButtonOffset = _addTabButton?.Bounds.Bottom ?? 0; - // markOffset = finalSize.Height; - // if (addButtonOffset > markOffset) { - // _tabsContainer.RowDefinitions[0].Height = GridLength.Star; - // } else { - // _tabsContainer.RowDefinitions[0].Height = GridLength.Auto; - // } - // } - // - // } - // } - #region IControlCustomStyle 实现 void IControlCustomStyle.HandleTemplateApplied(INameScope scope) { _addTabButton = scope.Find(CardTabControlTheme.AddTabButtonPart); _itemsPresenter = scope.Find(CardTabControlTheme.ItemsPresenterPart); - // _tabsContainer = scope.Find(CardTabControlTheme.TabsContainerPart); - _tabScrollViewer = scope.Find(CardTabControlTheme.CardTabStripScrollViewerPart); if (_addTabButton is not null) { _addTabButton.Click += HandleAddButtonClicked; } HandleSizeTypeChanged(); - // SetupCardTabStripContainer(); } #endregion @@ -193,57 +161,10 @@ public class CardTabControl : BaseTabControl, IControlCustomStyle protected override Size ArrangeOverride(Size finalSize) { var size = base.ArrangeOverride(finalSize); - // SetupCardTabStripContainer(finalSize); HandleTabStripPlacementChanged(); return size; } - // private void SetupCardTabStripContainer() - // { - // if (TabStripPlacement == Dock.Top || - // TabStripPlacement == Dock.Bottom) { - // if (_tabsContainer is not null) { - // _tabsContainer.Children.Clear(); - // _tabsContainer.RowDefinitions.Clear(); - // _tabsContainer.ColumnDefinitions = new ColumnDefinitions() - // { - // new ColumnDefinition(GridLength.Auto), - // new ColumnDefinition(GridLength.Auto), - // }; - // } - // - // if (_tabScrollViewer is not null) { - // Grid.SetColumn(_tabScrollViewer, 0); - // } - // - // if (_addTabButton is not null) { - // Grid.SetColumn(_addTabButton, 1); - // } - // - // _tabsContainer!.Children.Add(_tabScrollViewer!); - // _tabsContainer.Children.Add(_addTabButton!); - // } else { - // if (_tabsContainer is not null) { - // _tabsContainer.Children.Clear(); - // _tabsContainer.ColumnDefinitions.Clear(); - // _tabsContainer.RowDefinitions = new RowDefinitions() - // { - // new RowDefinition(GridLength.Auto), - // new RowDefinition(GridLength.Auto), - // }; - // } - // if (_tabScrollViewer is not null) { - // Grid.SetRow(_tabScrollViewer, 0); - // } - // - // if (_addTabButton is not null) { - // Grid.SetRow(_addTabButton, 1); - // } - // _tabsContainer!.Children.Add(_tabScrollViewer!); - // _tabsContainer.Children.Add(_addTabButton!); - // } - // } - private void HandleTabStripPlacementChanged() { if (TabStripPlacement == Dock.Top) { From c939b25884c1475d788a5ee07d09f9e26cc6d919 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sun, 4 Aug 2024 15:28:24 +0800 Subject: [PATCH 26/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20CardTabControl=20?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E5=B8=83=E5=B1=80=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成 CardTabControl 标签布局类 --- .../ShowCase/TabControlShowCase.axaml | 7 ++ .../TabControl/CardTabControlTheme.cs | 15 ++- .../TabControl/TabScrollContentPresenter.cs | 1 - .../TabControl/TabStrip/BaseTabStripTheme.cs | 26 ++-- .../TabControl/TabStrip/CardTabStrip.cs | 75 ------------ .../TabControl/TabStrip/CardTabStripTheme.cs | 25 ++-- .../TabControl/TabStrip/TabStripTheme.cs | 15 ++- .../TabControl/TabsContainerPanel.cs | 115 ++++++++++++++++++ 8 files changed, 171 insertions(+), 108 deletions(-) create mode 100644 src/AtomUI.Controls/TabControl/TabsContainerPanel.cs diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 0fc880d..3236865 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -103,6 +103,13 @@ Tab 2 Tab 3 + + + Tab 1 + Tab 2 + Tab 3 + + diff --git a/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs b/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs index e80e976..872976e 100644 --- a/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs +++ b/src/AtomUI.Controls/TabControl/CardTabControlTheme.cs @@ -27,14 +27,12 @@ internal class CardTabControlTheme : BaseTabControlTheme CreateTemplateParentBinding(alignWrapper, DockPanel.DockProperty, BaseTabControl.TabStripPlacementProperty); CreateTemplateParentBinding(alignWrapper, Panel.MarginProperty,TabControl.TabStripMarginProperty); - var cardTabStripContainer = new DockPanel() + var cardTabControlContainer = new TabsContainerPanel() { Name = TabsContainerPart, }; - cardTabStripContainer.RegisterInNameScope(scope); - - TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, - TabControlResourceKey.CardGutter); + cardTabControlContainer.RegisterInNameScope(scope); + CreateTemplateParentBinding(cardTabControlContainer, TabsContainerPanel.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var tabScrollViewer = new TabControlScrollViewer() { @@ -64,6 +62,7 @@ internal class CardTabControlTheme : BaseTabControlTheme BorderThickness = new Thickness(1), Icon = addTabIcon }; + DockPanel.SetDock(addTabButton, Dock.Right); CreateTemplateParentBinding(addTabButton, IconButton.BorderThicknessProperty, CardTabControl.CardBorderThicknessProperty); CreateTemplateParentBinding(addTabButton, IconButton.CornerRadiusProperty, CardTabControl.CardBorderRadiusProperty); @@ -73,10 +72,10 @@ internal class CardTabControlTheme : BaseTabControlTheme addTabButton.RegisterInNameScope(scope); - cardTabStripContainer.Children.Add(addTabButton); - cardTabStripContainer.Children.Add(tabScrollViewer); + cardTabControlContainer.TabScrollViewer = tabScrollViewer; + cardTabControlContainer.AddTabButton = addTabButton; - alignWrapper.Children.Add(cardTabStripContainer); + alignWrapper.Children.Add(cardTabControlContainer); container.Children.Add(alignWrapper); } diff --git a/src/AtomUI.Controls/TabControl/TabScrollContentPresenter.cs b/src/AtomUI.Controls/TabControl/TabScrollContentPresenter.cs index ad4b8ff..ac930f3 100644 --- a/src/AtomUI.Controls/TabControl/TabScrollContentPresenter.cs +++ b/src/AtomUI.Controls/TabControl/TabScrollContentPresenter.cs @@ -29,7 +29,6 @@ internal class TabScrollContentPresenter : ScrollContentPresenter, ICustomHitTes var x = Offset.X; var y = Offset.Y; var delta = e.Delta; - if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { delta = new Vector(delta.Y, delta.X); } diff --git a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs index 0e54da5..2a851c4 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs @@ -12,6 +12,8 @@ internal class BaseTabStripTheme : BaseControlTheme { public const string FrameDecoratorPart = "PART_FrameDecorator"; public const string ItemsPresenterPart = "PART_ItemsPresenter"; + public const string TabsContainerPart = "PART_TabsContainer"; + public const string AlignWrapperPart = "PART_AlignWrapper"; public BaseTabStripTheme(Type targetType) : base(targetType) { } @@ -50,9 +52,9 @@ internal class BaseTabStripTheme : BaseControlTheme // 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); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } topStyle.Add(tabAlignCenterStyle); @@ -69,9 +71,9 @@ internal class BaseTabStripTheme : BaseControlTheme // 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); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } rightStyle.Add(tabAlignCenterStyle); @@ -86,9 +88,9 @@ internal class BaseTabStripTheme : BaseControlTheme // 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); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } bottomStyle.Add(tabAlignCenterStyle); @@ -105,9 +107,9 @@ internal class BaseTabStripTheme : BaseControlTheme // 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); + var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart)); + tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center); + tabAlignCenterStyle.Add(tabsContainerStyle); } leftStyle.Add(tabAlignCenterStyle); diff --git a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs index 0bf0431..afc6ea9 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs @@ -127,37 +127,10 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle if (change.Property == SizeTypeProperty) { HandleSizeTypeChanged(); } else if (change.Property == TabStripPlacementProperty) { - SetupCardTabStripContainer(); HandleTabStripPlacementChanged(); } } - private void SetupCardTabStripContainer(Size finalSize) - { - if (_cardTabStripContainer is not null) { - double addButtonOffset = 0; - double markOffset = 0; - if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { - addButtonOffset = _addTabButton?.Bounds.Right ?? 0; - markOffset = finalSize.Width; - if (addButtonOffset > markOffset) { - _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Star; - } else { - _cardTabStripContainer.ColumnDefinitions[0].Width = GridLength.Auto; - } - } else { - addButtonOffset = _addTabButton?.Bounds.Bottom ?? 0; - markOffset = finalSize.Height; - if (addButtonOffset > markOffset) { - _cardTabStripContainer.RowDefinitions[0].Height = GridLength.Star; - } else { - _cardTabStripContainer.RowDefinitions[0].Height = GridLength.Auto; - } - } - - } - } - #region IControlCustomStyle 实现 void IControlCustomStyle.HandleTemplateApplied(INameScope scope) @@ -170,7 +143,6 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle _addTabButton.Click += HandleAddButtonClicked; } HandleSizeTypeChanged(); - SetupCardTabStripContainer(); } #endregion @@ -193,56 +165,9 @@ public class CardTabStrip : BaseTabStrip, IControlCustomStyle protected override Size ArrangeOverride(Size finalSize) { var size = base.ArrangeOverride(finalSize); - SetupCardTabStripContainer(finalSize); HandleTabStripPlacementChanged(); return size; } - - private void SetupCardTabStripContainer() - { - if (TabStripPlacement == Dock.Top || - TabStripPlacement == Dock.Bottom) { - if (_cardTabStripContainer is not null) { - _cardTabStripContainer.Children.Clear(); - _cardTabStripContainer.RowDefinitions.Clear(); - _cardTabStripContainer.ColumnDefinitions = new ColumnDefinitions() - { - new ColumnDefinition(GridLength.Auto), - new ColumnDefinition(GridLength.Auto), - }; - } - - if (_tabScrollViewer is not null) { - Grid.SetColumn(_tabScrollViewer, 0); - } - - if (_addTabButton is not null) { - Grid.SetColumn(_addTabButton, 1); - } - - _cardTabStripContainer!.Children.Add(_tabScrollViewer!); - _cardTabStripContainer.Children.Add(_addTabButton!); - } else { - if (_cardTabStripContainer is not null) { - _cardTabStripContainer.Children.Clear(); - _cardTabStripContainer.ColumnDefinitions.Clear(); - _cardTabStripContainer.RowDefinitions = new RowDefinitions() - { - new RowDefinition(GridLength.Auto), - new RowDefinition(GridLength.Auto), - }; - } - if (_tabScrollViewer is not null) { - Grid.SetRow(_tabScrollViewer, 0); - } - - if (_addTabButton is not null) { - Grid.SetRow(_addTabButton, 1); - } - _cardTabStripContainer!.Children.Add(_tabScrollViewer!); - _cardTabStripContainer.Children.Add(_addTabButton!); - } - } private void HandleTabStripPlacementChanged() { diff --git a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs index 73dd0ba..f3da7b1 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs @@ -12,22 +12,24 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class CardTabStripTheme : BaseTabStripTheme { - public const string AddTabButtonPart = "PART_AddTabButton"; - public const string CardTabStripContainerPart = "PART_CardTabStripContainer"; + public const string AddTabButtonPart = "PART_AddTabButton"; + public const string CardTabStripContainerPart = "PART_CardTabStripContainer"; public const string CardTabStripScrollViewerPart = "PART_CardTabStripScrollViewer"; public CardTabStripTheme() : base(typeof(CardTabStrip)) { } protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container) { - var cardTabStripContainer = new Grid() + var alignWrapper = new Panel() { - Name = CardTabStripContainerPart, + Name = AlignWrapperPart + }; + var cardTabStripContainer = new TabsContainerPanel() + { + Name = TabsContainerPart, }; cardTabStripContainer.RegisterInNameScope(scope); - - TokenResourceBinder.CreateTokenBinding(cardTabStripContainer, StackPanel.SpacingProperty, - TabControlResourceKey.CardGutter); + CreateTemplateParentBinding(cardTabStripContainer, TabsContainerPanel.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var tabScrollViewer = new TabStripScrollViewer() { @@ -65,10 +67,13 @@ internal class CardTabStripTheme : BaseTabStripTheme TokenResourceBinder.CreateGlobalResourceBinding(addTabButton, IconButton.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary); addTabButton.RegisterInNameScope(scope); + + cardTabStripContainer.TabScrollViewer = tabScrollViewer; + cardTabStripContainer.AddTabButton = addTabButton; - cardTabStripContainer.Children.Add(tabScrollViewer); - cardTabStripContainer.Children.Add(addTabButton); - container.Child = cardTabStripContainer; + alignWrapper.Children.Add(cardTabStripContainer); + + container.Child = alignWrapper; } private ItemsPresenter CreateTabStripContentPanel(INameScope scope) diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs index 35cdf06..9696f77 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripTheme.cs @@ -17,12 +17,23 @@ internal class TabStripTheme : BaseTabStripTheme protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container) { - var tabScrollViewer = new TabStripScrollViewer(); + var alignWrapper = new Panel() + { + Name = AlignWrapperPart + }; + alignWrapper.RegisterInNameScope(scope); + + var tabScrollViewer = new TabStripScrollViewer() + { + Name = TabsContainerPart + }; CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty); var contentPanel = CreateTabStripContentPanel(scope); tabScrollViewer.Content = contentPanel; tabScrollViewer.TabStrip = baseTabStrip; - container.Child = tabScrollViewer; + + alignWrapper.Children.Add(tabScrollViewer); + container.Child = alignWrapper; } private Panel CreateTabStripContentPanel(INameScope scope) diff --git a/src/AtomUI.Controls/TabControl/TabsContainerPanel.cs b/src/AtomUI.Controls/TabControl/TabsContainerPanel.cs new file mode 100644 index 0000000..1a36201 --- /dev/null +++ b/src/AtomUI.Controls/TabControl/TabsContainerPanel.cs @@ -0,0 +1,115 @@ +using Avalonia; +using Avalonia.Controls; + +namespace AtomUI.Controls; + +internal class TabsContainerPanel : Panel +{ + #region 公共属性定义 + + public static readonly DirectProperty TabScrollViewerProperty = + AvaloniaProperty.RegisterDirect(nameof(TabScrollViewer), + o => o.TabScrollViewer, + (o, v) => o.TabScrollViewer = v); + + private BaseTabScrollViewer? _tabScrollViewer; + public BaseTabScrollViewer? TabScrollViewer + { + get => _tabScrollViewer; + set => SetAndRaise(TabScrollViewerProperty, ref _tabScrollViewer, value); + } + + public static readonly DirectProperty AddTabButtonProperty = + AvaloniaProperty.RegisterDirect(nameof(AddTabButton), + o => o.AddTabButton, + (o, v) => o.AddTabButton = v); + + private IconButton? _addTabButton; + public IconButton? AddTabButton + { + get => _addTabButton; + set => SetAndRaise(AddTabButtonProperty, ref _addTabButton, value); + } + + #endregion + + #region 内部属性定义 + + internal static readonly DirectProperty TabStripPlacementProperty = + AvaloniaProperty.RegisterDirect(nameof(TabStripPlacement), + o => o.TabStripPlacement, + (o, v) => o.TabStripPlacement = v); + + private Dock _tabStripPlacement; + + internal Dock TabStripPlacement + { + get => _tabStripPlacement; + set => SetAndRaise(TabStripPlacementProperty, ref _tabStripPlacement, value); + } + + #endregion + + static TabsContainerPanel() + { + AffectsMeasure(TabScrollViewerProperty, AddTabButtonProperty); + AffectsArrange(TabStripPlacementProperty); + } + + protected override Size ArrangeOverride(Size arrangeSize) + { + // TODO 暂时不做验证,默认认为两个元素都存在 + // 理论上这里要报错,但是我们是内部使用 + if (_tabScrollViewer is not null && _addTabButton is not null) { + if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + var scrollViewerDesiredWidth = _tabScrollViewer.DesiredSize.Width; + var addTabButtonDesiredWidth = _addTabButton.DesiredSize.Width; + var totalDesiredWidth = scrollViewerDesiredWidth + addTabButtonDesiredWidth; + if (totalDesiredWidth > arrangeSize.Width) { + _tabScrollViewer.Arrange(new Rect(new Point(0, 0), new Size(arrangeSize.Width - addTabButtonDesiredWidth, arrangeSize.Height))); + _addTabButton.Arrange(new Rect(new Point(arrangeSize.Width - addTabButtonDesiredWidth, 0), _addTabButton.DesiredSize)); + } else { + _tabScrollViewer.Arrange(new Rect(new Point(0, 0), _tabScrollViewer.DesiredSize)); + _addTabButton.Arrange(new Rect(new Point(scrollViewerDesiredWidth, 0), _addTabButton.DesiredSize)); + } + } else { + var scrollViewerDesiredHeight = _tabScrollViewer.DesiredSize.Height; + var addTabButtonDesiredHeight = _addTabButton.DesiredSize.Height; + var totalDesiredHeight = scrollViewerDesiredHeight + addTabButtonDesiredHeight; + if (totalDesiredHeight > arrangeSize.Height) { + _tabScrollViewer.Arrange(new Rect(new Point(0, 0), new Size(arrangeSize.Width, arrangeSize.Height - addTabButtonDesiredHeight))); + _addTabButton.Arrange(new Rect(new Point(0, arrangeSize.Width - addTabButtonDesiredHeight), _addTabButton.DesiredSize)); + } else { + _tabScrollViewer.Arrange(new Rect(new Point(0, 0), _tabScrollViewer.DesiredSize)); + _addTabButton.Arrange(new Rect(new Point(scrollViewerDesiredHeight, 0), _addTabButton.DesiredSize)); + } + } + } + + return (arrangeSize); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change.Property == TabScrollViewerProperty) { + var oldScrollViewer = change.GetOldValue(); + if (oldScrollViewer is not null) { + Children.Remove(oldScrollViewer); + } + + if (TabScrollViewer is not null) { + Children.Add(TabScrollViewer); + } + } else if (change.Property == AddTabButtonProperty) { + var oldAddTabButton = change.GetOldValue(); + if (oldAddTabButton is not null) { + Children.Remove(oldAddTabButton); + } + + if (AddTabButton is not null) { + Children.Add(AddTabButton); + } + } + } +} \ No newline at end of file From 75bc4e3baffe0769bb0d4157c074faa10f65149b Mon Sep 17 00:00:00 2001 From: polarboy Date: Sun, 4 Aug 2024 16:00:44 +0800 Subject: [PATCH 27/28] =?UTF-8?q?=E9=87=8D=E6=9E=84=20TabControl=20?= =?UTF-8?q?=E6=BA=A2=E5=87=BA=E4=B8=8A=E4=B8=8B=E6=96=87=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构 TabControl 溢出上下文菜单 --- .../ShowCase/TabControlShowCase.axaml | 70 ++++++++++++++ ...TabMenuItem.cs => BaseOverflowMenuItem.cs} | 29 +++--- ...mTheme.cs => BaseOverflowMenuItemTheme.cs} | 20 ++-- .../TabControl/TabControlOverflowMenuItem.cs | 22 +++++ .../TabControl/TabControlScrollViewer.cs | 96 ++++++++++++++++++- .../TabStrip/TabStripOverflowMenuItem.cs | 22 +++++ .../TabStrip/TabStripScrollViewer.cs | 6 +- 7 files changed, 234 insertions(+), 31 deletions(-) rename src/AtomUI.Controls/TabControl/{OverflowTabMenuItem.cs => BaseOverflowMenuItem.cs} (60%) rename src/AtomUI.Controls/TabControl/{OverflowTabMenuItemTheme.cs => BaseOverflowMenuItemTheme.cs} (87%) create mode 100644 src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs create mode 100644 src/AtomUI.Controls/TabControl/TabStrip/TabStripOverflowMenuItem.cs diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 3236865..67f084a 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -59,6 +59,76 @@ + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + + + + + + + + + + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + Content of Tab Pane 4 + Content of Tab Pane 5 + Content of Tab Pane 6 + Content of Tab Pane 7 + Content of Tab Pane 8 + Content of Tab Pane 9 + Content of Tab Pane 10 + Content of Tab Pane 11 + Content of Tab Pane 12 + Content of Tab Pane 13 + Content of Tab Pane 14 + Content of Tab Pane 15 + Content of Tab Pane 16 + Content of Tab Pane 17 + Content of Tab Pane 18 + Content of Tab Pane 19 + Content of Tab Pane 20 + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + Content of Tab Pane 4 + Content of Tab Pane 5 + Content of Tab Pane 6 + Content of Tab Pane 7 + Content of Tab Pane 8 + Content of Tab Pane 9 + Content of Tab Pane 10 + Content of Tab Pane 11 + Content of Tab Pane 12 + Content of Tab Pane 13 + Content of Tab Pane 14 + Content of Tab Pane 15 + Content of Tab Pane 16 + Content of Tab Pane 17 + Content of Tab Pane 18 + Content of Tab Pane 19 + Content of Tab Pane 20 + + + + diff --git a/src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs similarity index 60% rename from src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs rename to src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs index 0754eed..33329ef 100644 --- a/src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs +++ b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs @@ -6,12 +6,12 @@ using Avalonia.Threading; namespace AtomUI.Controls; -internal class OverflowTabMenuItem : MenuItem +internal class BaseOverflowMenuItem : MenuItem { #region 公共属性 - public static readonly DirectProperty IsClosableProperty = - AvaloniaProperty.RegisterDirect(nameof(IsClosable), + public static readonly DirectProperty IsClosableProperty = + AvaloniaProperty.RegisterDirect(nameof(IsClosable), o => o.IsClosable, (o, v) => o.IsClosable = v); @@ -24,8 +24,6 @@ internal class OverflowTabMenuItem : MenuItem get => _isClosable; set => SetAndRaise(IsClosableProperty, ref _isClosable, value); } - - public TabStripItem? TabStripItem { get; set; } public event EventHandler? CloseTab { @@ -40,29 +38,26 @@ internal class OverflowTabMenuItem : MenuItem protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _iconButton = e.NameScope.Find(OverflowTabMenuItemTheme.ItemCloseButtonPart); + _iconButton = e.NameScope.Find(BaseOverflowMenuItemTheme.ItemCloseButtonPart); if (_iconButton is not null) { _iconButton.Click += (sender, args) => { - if (Parent is MenuBase menu) { - var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabStripItem!); - RaiseEvent(eventArgs); - Dispatcher.UIThread.Post(() => - { - menu.Close(); - }); - } + NotifyCloseRequest(); }; } } + + protected virtual void NotifyCloseRequest() + { + } } internal class CloseTabRequestEventArgs : RoutedEventArgs { - public CloseTabRequestEventArgs(RoutedEvent routedEvent, TabStripItem stripItem) + public CloseTabRequestEventArgs(RoutedEvent routedEvent, object tabItem) : base(routedEvent) { - TabStripItem = stripItem; + TabItem = tabItem; } - public TabStripItem TabStripItem { get; } + public object TabItem { get; } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItemTheme.cs similarity index 87% rename from src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs rename to src/AtomUI.Controls/TabControl/BaseOverflowMenuItemTheme.cs index 76392a3..5c08ae9 100644 --- a/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItemTheme.cs @@ -13,21 +13,21 @@ using Avalonia.Styling; namespace AtomUI.Controls; [ControlThemeProvider] -internal class OverflowTabMenuItemTheme : BaseControlTheme +internal class BaseOverflowMenuItemTheme : BaseControlTheme { public const string ItemDecoratorPart = "PART_ItemDecorator"; public const string MainContainerPart = "PART_MainContainer"; public const string ItemTextPresenterPart = "PART_ItemTextPresenter"; public const string ItemCloseButtonPart = "PART_ItemCloseIcon"; - public OverflowTabMenuItemTheme() - : base(typeof(OverflowTabMenuItem)) + public BaseOverflowMenuItemTheme() + : base(typeof(BaseOverflowMenuItem)) { } protected override IControlTemplate BuildControlTemplate() { - return new FuncControlTemplate((item, scope) => + return new FuncControlTemplate((item, scope) => { var container = new Border() { @@ -61,8 +61,8 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme Grid.SetColumn(itemTextPresenter, 0); TokenResourceBinder.CreateTokenBinding(itemTextPresenter, ContentPresenter.MarginProperty, MenuResourceKey.ItemMargin); - CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, OverflowTabMenuItem.HeaderProperty); - CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, OverflowTabMenuItem.HeaderTemplateProperty); + CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, BaseOverflowMenuItem.HeaderProperty); + CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, BaseOverflowMenuItem.HeaderTemplateProperty); itemTextPresenter.RegisterInNameScope(scope); @@ -82,7 +82,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme }; - CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, OverflowTabMenuItem.IsClosableProperty); + CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, BaseOverflowMenuItem.IsClosableProperty); TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon); TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover); @@ -110,7 +110,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme private void BuildCommonStyle(Style commonStyle) { - commonStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemColor); + commonStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemColor); { var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); borderStyle.Add(Border.MinHeightProperty, MenuResourceKey.ItemHeight); @@ -122,7 +122,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme // Hover 状态 var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver)); - hoverStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor); + hoverStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor); { var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart)); borderStyle.Add(Border.BackgroundProperty, MenuResourceKey.ItemHoverBg); @@ -134,7 +134,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme private void BuildDisabledStyle() { var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled)); - disabledStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor); + disabledStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor); Add(disabledStyle); } diff --git a/src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs b/src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs new file mode 100644 index 0000000..17c5160 --- /dev/null +++ b/src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs @@ -0,0 +1,22 @@ +using Avalonia.Controls; +using Avalonia.Threading; + +namespace AtomUI.Controls; + +internal class TabControlOverflowMenuItem : BaseOverflowMenuItem +{ + protected override Type StyleKeyOverride => typeof(BaseOverflowMenuItem); + public TabItem? TabItem { get; set; } + + protected override void NotifyCloseRequest() + { + if (Parent is MenuBase menu) { + var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabItem!); + RaiseEvent(eventArgs); + Dispatcher.UIThread.Post(() => + { + menu.Close(); + }); + } + } +} diff --git a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs index 88455b3..c23643c 100644 --- a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs @@ -1,4 +1,9 @@ -namespace AtomUI.Controls; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Threading; + +namespace AtomUI.Controls; internal class TabControlScrollViewer : BaseTabScrollViewer { @@ -9,4 +14,93 @@ internal class TabControlScrollViewer : BaseTabScrollViewer #endregion protected override Type StyleKeyOverride => typeof(BaseTabScrollViewer); + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + if (_menuIndicator is not null) { + _menuIndicator.Click += HandleMenuIndicator; + } + } + + private void HandleMenuIndicator(object? sender, RoutedEventArgs args) + { + if (_menuFlyout is null) { + _menuFlyout = new MenuFlyout(); + } + + if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + _menuFlyout.Placement = PlacementMode.Bottom; + } else { + _menuFlyout.Placement = PlacementMode.Right; + } + + // 收集没有完全显示的 Tab 列表 + _menuFlyout.Items.Clear(); + if (TabControl is not null) { + for (int i = 0; i < TabControl.ItemCount; i++) { + var itemContainer = TabControl.ContainerFromIndex(i)!; + if (itemContainer is TabItem tabItem) { + var itemBounds = itemContainer.Bounds; + var left = Math.Floor(itemBounds.Left - Offset.X); + var right = Math.Floor(itemBounds.Right - Offset.X); + if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + if (left < 0 || right > Viewport.Width) { + var menuItem = new TabControlOverflowMenuItem + { + Header = tabItem.Header, + TabItem = tabItem, + IsClosable = tabItem.IsClosable + }; + menuItem.Click += HandleMenuItemClicked; + menuItem.CloseTab += HandleCloseTabRequest; + _menuFlyout.Items.Add(menuItem); + } + } + } + } + + if (_menuFlyout.Items.Count > 0) { + _menuFlyout.ShowAt(_menuIndicator!); + } + } + } + + private void HandleMenuItemClicked(object? sender, RoutedEventArgs args) + { + if (TabControl is not null) { + Dispatcher.UIThread.Post(sender => + { + if (sender is TabControlOverflowMenuItem tabControlMenuItem) { + var tabItem = tabControlMenuItem.TabItem; + if (tabItem is not null) { + tabItem.BringIntoView(); + TabControl.SelectedItem = tabItem; + } + } + }, sender); + } + } + + private void HandleCloseTabRequest(object? sender, RoutedEventArgs args) + { + if (sender is TabControlOverflowMenuItem tabControlMenuItem) { + if (TabControl is not null) { + if (TabControl.SelectedItem is TabItem selectedItem) { + if (selectedItem == tabControlMenuItem.TabItem) { + var selectedIndex = TabControl.SelectedIndex; + object? newSelectedItem = null; + if (selectedIndex != 0) { + newSelectedItem = TabControl.Items[--selectedIndex]; + } + TabControl.Items.Remove(tabControlMenuItem.TabItem); + TabControl.SelectedItem = newSelectedItem; + } else { + TabControl.Items.Remove(tabControlMenuItem.TabItem); + } + } + } + + } + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripOverflowMenuItem.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripOverflowMenuItem.cs new file mode 100644 index 0000000..b473e9c --- /dev/null +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripOverflowMenuItem.cs @@ -0,0 +1,22 @@ +using Avalonia.Controls; +using Avalonia.Threading; + +namespace AtomUI.Controls; + +internal class TabStripOverflowMenuItem : BaseOverflowMenuItem +{ + protected override Type StyleKeyOverride => typeof(BaseOverflowMenuItem); + public TabStripItem? TabStripItem { get; set; } + + protected override void NotifyCloseRequest() + { + if (Parent is MenuBase menu) { + var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabStripItem!); + RaiseEvent(eventArgs); + Dispatcher.UIThread.Post(() => + { + menu.Close(); + }); + } + } +} diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs index 4a1c07b..514e9a6 100644 --- a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs @@ -46,7 +46,7 @@ internal class TabStripScrollViewer : BaseTabScrollViewer var right = Math.Floor(itemBounds.Right - Offset.X); if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { if (left < 0 || right > Viewport.Width) { - var menuItem = new OverflowTabMenuItem() + var menuItem = new TabStripOverflowMenuItem() { Header = tabStripItem.Content, TabStripItem = tabStripItem, @@ -71,7 +71,7 @@ internal class TabStripScrollViewer : BaseTabScrollViewer if (TabStrip is not null) { Dispatcher.UIThread.Post(sender => { - if (sender is OverflowTabMenuItem tabStripMenuItem) { + if (sender is TabStripOverflowMenuItem tabStripMenuItem) { var tabStripItem = tabStripMenuItem.TabStripItem; if (tabStripItem is not null) { tabStripItem.BringIntoView(); @@ -84,7 +84,7 @@ internal class TabStripScrollViewer : BaseTabScrollViewer private void HandleCloseTabRequest(object? sender, RoutedEventArgs args) { - if (sender is OverflowTabMenuItem tabStripMenuItem) { + if (sender is TabStripOverflowMenuItem tabStripMenuItem) { if (TabStrip is not null) { if (TabStrip.SelectedItem is TabStripItem selectedItem) { if (selectedItem == tabStripMenuItem.TabStripItem) { From c79f878b644f642e5e1217d345ec62522302d924 Mon Sep 17 00:00:00 2001 From: polarboy Date: Sun, 4 Aug 2024 17:53:39 +0800 Subject: [PATCH 28/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20TabContol=20?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E5=9B=9B=E4=B8=AA=E6=96=B9=E5=90=91=E7=9A=84?= =?UTF-8?q?=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成 TabContol 控件四个方向的切换 --- .../ShowCase/TabControlShowCase.axaml | 136 +++++++++++++++++- .../ShowCase/TabControlShowCase.axaml.cs | 110 ++++++++++++-- .../TabControl/BaseTabControl.cs | 8 +- .../TabControl/BaseTabScrollViewer.cs | 8 +- .../TabControl/BaseTabScrollViewerTheme.cs | 50 ++++--- .../TabControl/TabControlScrollViewer.cs | 40 ++++-- .../TabControl/TabsContainerPanel.cs | 20 ++- 7 files changed, 312 insertions(+), 60 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml index 67f084a..42a4585 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml @@ -68,12 +68,7 @@ Content of Tab Pane 2 Content of Tab Pane 3 - - - - - - + @@ -129,6 +124,135 @@ + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + + + + + + + + + Tab position: + + Top + Bottom + Left + Right + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + Content of Tab Pane 4 + Content of Tab Pane 5 + Content of Tab Pane 6 + Content of Tab Pane 7 + Content of Tab Pane 8 + + + + + + + + + Tab position: + + Top + Bottom + Left + Right + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + Content of Tab Pane 4 + Content of Tab Pane 5 + Content of Tab Pane 6 + Content of Tab Pane 7 + Content of Tab Pane 8 + Content of Tab Pane 9 + Content of Tab Pane 10 + Content of Tab Pane 11 + Content of Tab Pane 12 + + + + + + + + + Tab position: + + Small + Middle + Large + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + Content of Tab Pane 4 + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + Content of Tab Pane 4 + + + + + + + + Content of Tab Pane 1 + Content of Tab Pane 2 + Content of Tab Pane 3 + Content of Tab Pane 4 + + + diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs index a6a1357..550d795 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs +++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml.cs @@ -2,11 +2,14 @@ using AtomUI.Controls; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; +using TabItem = AtomUI.Controls.TabItem; namespace AtomUI.Demo.Desktop.ShowCase; public partial class TabControlShowCase : UserControl { + #region TabStrip + public static readonly StyledProperty PositionTabStripPlacementProperty = AvaloniaProperty.Register(nameof(PositionTabStripPlacement), Dock.Top); @@ -33,18 +36,57 @@ public partial class TabControlShowCase : UserControl get => GetValue(SizeTypeTabStripProperty); set => SetValue(SizeTypeTabStripProperty, value); } + + #endregion + + #region TabControl + + public static readonly StyledProperty PositionTabControlPlacementProperty = + AvaloniaProperty.Register(nameof(PositionTabControlPlacement), Dock.Top); + public static readonly StyledProperty PositionCardTabControlPlacementProperty = + AvaloniaProperty.Register(nameof(PositionCardTabControlPlacement), Dock.Top); + + public static readonly StyledProperty SizeTypeTabControlProperty = + AvaloniaProperty.Register(nameof(SizeTypeTabControl), SizeType.Middle); + + public Dock PositionTabControlPlacement + { + get => GetValue(PositionTabControlPlacementProperty); + set => SetValue(PositionTabControlPlacementProperty, value); + } + + public Dock PositionCardTabControlPlacement + { + get => GetValue(PositionCardTabControlPlacementProperty); + set => SetValue(PositionCardTabControlPlacementProperty, value); + } + + public SizeType SizeTypeTabControl + { + get => GetValue(SizeTypeTabControlProperty); + set => SetValue(SizeTypeTabControlProperty, value); + } + + #endregion + public TabControlShowCase() { InitializeComponent(); DataContext = this; - PositionTabStripOptionGroup.OptionCheckedChanged += HandlePlacementOptionCheckedChanged; - PositionCardTabStripOptionGroup.OptionCheckedChanged += HandleCardPlacementOptionCheckedChanged; - SizeTypeTabStripOptionGroup.OptionCheckedChanged += HandleSizeTypeOptionCheckedChanged; - AddTabDemoStrip.AddTabRequest += HandleAddTabRequest; + PositionTabStripOptionGroup.OptionCheckedChanged += HandleTabStripPlacementOptionCheckedChanged; + PositionCardTabStripOptionGroup.OptionCheckedChanged += HandleCardTabStripPlacementOptionCheckedChanged; + SizeTypeTabStripOptionGroup.OptionCheckedChanged += HandleTabStripSizeTypeOptionCheckedChanged; + AddTabDemoStrip.AddTabRequest += HandleTabStripAddTabRequest; + + PositionTabControlOptionGroup.OptionCheckedChanged += HandleTabControlPlacementOptionCheckedChanged; + PositionCardTabControlOptionGroup.OptionCheckedChanged += HandleCardTabControlPlacementOptionCheckedChanged; + SizeTypeTabControlOptionGroup.OptionCheckedChanged += HandleTabControlSizeTypeOptionCheckedChanged; + AddTabDemoTabControl.AddTabRequest += HandleTabControlAddTabRequest; } - private void HandlePlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + #region TabStrip + private void HandleTabStripPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) { if (args.Index == 0) { PositionTabStripPlacement = Dock.Top; @@ -57,7 +99,7 @@ public partial class TabControlShowCase : UserControl } } - private void HandleCardPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + private void HandleCardTabStripPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) { if (args.Index == 0) { PositionCardTabStripPlacement = Dock.Top; @@ -70,7 +112,7 @@ public partial class TabControlShowCase : UserControl } } - private void HandleSizeTypeOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + private void HandleTabStripSizeTypeOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) { if (args.Index == 0) { SizeTypeTabStrip = SizeType.Small; @@ -81,7 +123,7 @@ public partial class TabControlShowCase : UserControl } } - private void HandleAddTabRequest(object? sender, RoutedEventArgs args) + private void HandleTabStripAddTabRequest(object? sender, RoutedEventArgs args) { var index = AddTabDemoStrip.ItemCount; AddTabDemoStrip.Items.Add(new TabStripItem() @@ -90,4 +132,56 @@ public partial class TabControlShowCase : UserControl IsClosable = true }); } + #endregion + + + #region TabControl + private void HandleTabControlPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + { + if (args.Index == 0) { + PositionTabControlPlacement = Dock.Top; + } else if (args.Index == 1) { + PositionTabControlPlacement = Dock.Bottom; + } else if (args.Index == 2) { + PositionTabControlPlacement = Dock.Left; + } else { + PositionTabControlPlacement = Dock.Right; + } + } + + private void HandleCardTabControlPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + { + if (args.Index == 0) { + PositionCardTabControlPlacement = Dock.Top; + } else if (args.Index == 1) { + PositionCardTabControlPlacement = Dock.Bottom; + } else if (args.Index == 2) { + PositionCardTabControlPlacement = Dock.Left; + } else { + PositionCardTabControlPlacement = Dock.Right; + } + } + + private void HandleTabControlSizeTypeOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args) + { + if (args.Index == 0) { + SizeTypeTabControl = SizeType.Small; + } else if (args.Index == 1) { + SizeTypeTabControl = SizeType.Middle; + } else { + SizeTypeTabControl = SizeType.Large; + } + } + + private void HandleTabControlAddTabRequest(object? sender, RoutedEventArgs args) + { + var index = AddTabDemoTabControl.ItemCount; + AddTabDemoTabControl.Items.Add(new TabItem() + { + Header = $"new tab {index}", + Content = $"new tab content {index}", + IsClosable = true + }); + } + #endregion } \ No newline at end of file diff --git a/src/AtomUI.Controls/TabControl/BaseTabControl.cs b/src/AtomUI.Controls/TabControl/BaseTabControl.cs index 47a0756..bf729a2 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabControl.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabControl.cs @@ -172,15 +172,9 @@ public class BaseTabControl : AvaloniaTabControl } } - protected override Size ArrangeOverride(Size finalSize) - { - var size = base.ArrangeOverride(finalSize); - SetupTabStripBorderPoints(); - return size; - } - public override void Render(DrawingContext context) { + SetupTabStripBorderPoints(); var borderThickness = BorderThickness.Left; using var optionState = context.PushRenderOptions(new RenderOptions() { diff --git a/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs b/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs index 1a6b6d2..13a8134 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabScrollViewer.cs @@ -100,12 +100,14 @@ internal abstract class BaseTabScrollViewer : ScrollViewer if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { if (_startEdgeIndicator is not null) { _startEdgeIndicator.Height = Presenter.DesiredSize.Height; + _startEdgeIndicator.Width = _menuEdgeThickness; _startEdgeIndicator.ZIndex = EdgeIndicatorZIndex; _startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, true); } if (_endEdgeIndicator is not null) { _endEdgeIndicator.Height = Presenter.DesiredSize.Height; + _endEdgeIndicator.Width = _menuEdgeThickness; _endEdgeIndicator.Margin = new Thickness(0, 0, _menuEdgeThickness, 0); _endEdgeIndicator.ZIndex = EdgeIndicatorZIndex; _endEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false); @@ -113,13 +115,15 @@ internal abstract class BaseTabScrollViewer : ScrollViewer } else { if (_startEdgeIndicator is not null) { _startEdgeIndicator.Width = Presenter.DesiredSize.Width; + _startEdgeIndicator.Height = _menuEdgeThickness; _startEdgeIndicator.ZIndex = EdgeIndicatorZIndex; - _startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false); + _startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, true); } if (_endEdgeIndicator is not null) { _endEdgeIndicator.Width = Presenter.DesiredSize.Width; - _endEdgeIndicator.Margin = new Thickness(0, _menuEdgeThickness, 0, 0); + _endEdgeIndicator.Height = _menuEdgeThickness; + _endEdgeIndicator.Margin = new Thickness(0, 0, 0, _menuEdgeThickness); _endEdgeIndicator.ZIndex = EdgeIndicatorZIndex; _endEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false); } diff --git a/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs index d3b283c..6f5677c 100644 --- a/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs +++ b/src/AtomUI.Controls/TabControl/BaseTabScrollViewerTheme.cs @@ -61,9 +61,6 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var scrollViewContent = CreateScrollContentPresenter(); - DockPanel.SetDock(scrollViewContent, Dock.Left); - DockPanel.SetDock(menuIndicator, Dock.Right); - scrollViewLayout.Children.Add(menuIndicator); scrollViewLayout.Children.Add(scrollViewContent); @@ -72,8 +69,6 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var startEdgeIndicator = new Border() { Name = ScrollStartEdgeIndicatorPart, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, IsHitTestVisible = false }; startEdgeIndicator.RegisterInNameScope(scope); @@ -82,8 +77,6 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var endEdgeIndicator = new Border() { - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Top, Name = ScrollEndEdgeIndicatorPart, IsHitTestVisible = false }; @@ -131,7 +124,7 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var topPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Top)); { var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); + new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart)); contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left); var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); @@ -140,10 +133,15 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); startEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); + startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch); topPlacementStyle.Add(startEdgeIndicatorStyle); var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart)); endEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); + endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right); + endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch); + topPlacementStyle.Add(endEdgeIndicatorStyle); topPlacementStyle.Add(menuIndicatorStyle); @@ -156,7 +154,7 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme { var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); + new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart)); contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top); var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); @@ -165,11 +163,15 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); startEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(startEdgeIndicatorStyle); + startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); + rightPlacementStyle.Add(startEdgeIndicatorStyle); - var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart)); endEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(endEdgeIndicatorStyle); + endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom); + rightPlacementStyle.Add(endEdgeIndicatorStyle); rightPlacementStyle.Add(menuIndicatorStyle); rightPlacementStyle.Add(contentPresenterStyle); @@ -181,7 +183,7 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme { var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); + new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart)); contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left); var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); @@ -190,11 +192,15 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); startEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(startEdgeIndicatorStyle); + startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left); + startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch); + bottomPlacementStyle.Add(startEdgeIndicatorStyle); - var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart)); endEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(endEdgeIndicatorStyle); + endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right); + endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch); + bottomPlacementStyle.Add(endEdgeIndicatorStyle); bottomPlacementStyle.Add(menuIndicatorStyle); bottomPlacementStyle.Add(contentPresenterStyle); @@ -206,7 +212,7 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme { var contentPresenterStyle = - new Style(selector => selector.Nesting().Template().OfType()); + new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart)); contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top); var menuIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart)); @@ -215,11 +221,15 @@ internal class BaseTabScrollViewerTheme : BaseControlTheme var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); startEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(startEdgeIndicatorStyle); + startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top); + leftPlacementStyle.Add(startEdgeIndicatorStyle); - var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart)); + var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart)); endEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness); - topPlacementStyle.Add(endEdgeIndicatorStyle); + endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch); + endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom); + leftPlacementStyle.Add(endEdgeIndicatorStyle); leftPlacementStyle.Add(menuIndicatorStyle); leftPlacementStyle.Add(contentPresenterStyle); diff --git a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs index c23643c..fa6ee49 100644 --- a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs +++ b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs @@ -29,8 +29,12 @@ internal class TabControlScrollViewer : BaseTabScrollViewer _menuFlyout = new MenuFlyout(); } - if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + if (TabStripPlacement == Dock.Top) { _menuFlyout.Placement = PlacementMode.Bottom; + } else if (TabStripPlacement == Dock.Bottom) { + _menuFlyout.Placement = PlacementMode.Top; + } else if (TabStripPlacement == Dock.Right) { + _menuFlyout.Placement = PlacementMode.Left; } else { _menuFlyout.Placement = PlacementMode.Right; } @@ -41,21 +45,33 @@ internal class TabControlScrollViewer : BaseTabScrollViewer for (int i = 0; i < TabControl.ItemCount; i++) { var itemContainer = TabControl.ContainerFromIndex(i)!; if (itemContainer is TabItem tabItem) { + bool needAddToMenu = false; var itemBounds = itemContainer.Bounds; - var left = Math.Floor(itemBounds.Left - Offset.X); - var right = Math.Floor(itemBounds.Right - Offset.X); if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) { + var left = Math.Floor(itemBounds.Left - Offset.X); + var right = Math.Floor(itemBounds.Right - Offset.X); if (left < 0 || right > Viewport.Width) { - var menuItem = new TabControlOverflowMenuItem - { - Header = tabItem.Header, - TabItem = tabItem, - IsClosable = tabItem.IsClosable - }; - menuItem.Click += HandleMenuItemClicked; - menuItem.CloseTab += HandleCloseTabRequest; - _menuFlyout.Items.Add(menuItem); + needAddToMenu = true; } + } else { + var top = Math.Floor(itemBounds.Top - Offset.Y); + var bottom = Math.Floor(itemBounds.Bottom - Offset.Y); + + if (top < 0 || bottom > Viewport.Height) { + needAddToMenu = true; + } + } + + if (needAddToMenu) { + var menuItem = new TabControlOverflowMenuItem + { + Header = tabItem.Header, + TabItem = tabItem, + IsClosable = tabItem.IsClosable + }; + menuItem.Click += HandleMenuItemClicked; + menuItem.CloseTab += HandleCloseTabRequest; + _menuFlyout.Items.Add(menuItem); } } } diff --git a/src/AtomUI.Controls/TabControl/TabsContainerPanel.cs b/src/AtomUI.Controls/TabControl/TabsContainerPanel.cs index 1a36201..0c9885c 100644 --- a/src/AtomUI.Controls/TabControl/TabsContainerPanel.cs +++ b/src/AtomUI.Controls/TabControl/TabsContainerPanel.cs @@ -65,28 +65,38 @@ internal class TabsContainerPanel : Panel var scrollViewerDesiredWidth = _tabScrollViewer.DesiredSize.Width; var addTabButtonDesiredWidth = _addTabButton.DesiredSize.Width; var totalDesiredWidth = scrollViewerDesiredWidth + addTabButtonDesiredWidth; + + var btnOffsetY = 0d; + if (TabStripPlacement == Dock.Top) { + btnOffsetY = arrangeSize.Height - _addTabButton.DesiredSize.Height; + } + if (totalDesiredWidth > arrangeSize.Width) { _tabScrollViewer.Arrange(new Rect(new Point(0, 0), new Size(arrangeSize.Width - addTabButtonDesiredWidth, arrangeSize.Height))); - _addTabButton.Arrange(new Rect(new Point(arrangeSize.Width - addTabButtonDesiredWidth, 0), _addTabButton.DesiredSize)); + _addTabButton.Arrange(new Rect(new Point(arrangeSize.Width - addTabButtonDesiredWidth, btnOffsetY), _addTabButton.DesiredSize)); } else { _tabScrollViewer.Arrange(new Rect(new Point(0, 0), _tabScrollViewer.DesiredSize)); - _addTabButton.Arrange(new Rect(new Point(scrollViewerDesiredWidth, 0), _addTabButton.DesiredSize)); + _addTabButton.Arrange(new Rect(new Point(scrollViewerDesiredWidth, btnOffsetY), _addTabButton.DesiredSize)); } } else { var scrollViewerDesiredHeight = _tabScrollViewer.DesiredSize.Height; var addTabButtonDesiredHeight = _addTabButton.DesiredSize.Height; var totalDesiredHeight = scrollViewerDesiredHeight + addTabButtonDesiredHeight; + var btnOffsetX = 0d; + if (TabStripPlacement == Dock.Left) { + btnOffsetX = arrangeSize.Width - _addTabButton.DesiredSize.Width; + } if (totalDesiredHeight > arrangeSize.Height) { _tabScrollViewer.Arrange(new Rect(new Point(0, 0), new Size(arrangeSize.Width, arrangeSize.Height - addTabButtonDesiredHeight))); - _addTabButton.Arrange(new Rect(new Point(0, arrangeSize.Width - addTabButtonDesiredHeight), _addTabButton.DesiredSize)); + _addTabButton.Arrange(new Rect(new Point(btnOffsetX, arrangeSize.Height - addTabButtonDesiredHeight), _addTabButton.DesiredSize)); } else { _tabScrollViewer.Arrange(new Rect(new Point(0, 0), _tabScrollViewer.DesiredSize)); - _addTabButton.Arrange(new Rect(new Point(scrollViewerDesiredHeight, 0), _addTabButton.DesiredSize)); + _addTabButton.Arrange(new Rect(new Point(btnOffsetX, scrollViewerDesiredHeight), _addTabButton.DesiredSize)); } } } - return (arrangeSize); + return arrangeSize; } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)