diff --git a/src/AtomUI.Controls/TabControl/BaseTabControl.cs b/src/AtomUI.Controls/TabControl/BaseTabControl.cs index b22da2563..9f974eb07 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 000000000..f2572c262 --- /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