完成tabstrip 阴影

This commit is contained in:
polarboy 2024-08-01 09:45:49 +08:00
parent d1867a00a1
commit 1c7c8e458a
8 changed files with 593 additions and 1 deletions

View File

@ -55,5 +55,37 @@
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Slide"
Description="In order to fit in more tabs, they can slide left and right (or up and down).">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
<atom:TabStripItem>Tab 4</atom:TabStripItem>
<atom:TabStripItem>Tab 5</atom:TabStripItem>
<atom:TabStripItem>Tab 6</atom:TabStripItem>
<atom:TabStripItem>Tab 7</atom:TabStripItem>
<atom:TabStripItem>Tab 8</atom:TabStripItem>
<atom:TabStripItem>Tab 9</atom:TabStripItem>
<atom:TabStripItem>Tab 10</atom:TabStripItem>
<atom:TabStripItem>Tab 11</atom:TabStripItem>
<atom:TabStripItem>Tab 12</atom:TabStripItem>
<atom:TabStripItem>Tab 13</atom:TabStripItem>
<atom:TabStripItem>Tab 14</atom:TabStripItem>
<atom:TabStripItem>Tab 15</atom:TabStripItem>
<atom:TabStripItem>Tab 16</atom:TabStripItem>
<atom:TabStripItem>Tab 17</atom:TabStripItem>
<atom:TabStripItem>Tab 18</atom:TabStripItem>
<atom:TabStripItem>Tab 19</atom:TabStripItem>
<atom:TabStripItem>Tab 20</atom:TabStripItem>
<atom:TabStripItem>Tab 21</atom:TabStripItem>
<atom:TabStripItem>Tab 22</atom:TabStripItem>
<atom:TabStripItem>Tab 23</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
</showcase:ShowCasePanel>
</UserControl>

View File

@ -315,6 +315,9 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey ItemSelectedColor = new TokenResourceKey("TabControl.ItemSelectedColor");
public static readonly TokenResourceKey CardGutter = new TokenResourceKey("TabControl.CardGutter");
public static readonly TokenResourceKey ItemIconMargin = new TokenResourceKey("TabControl.ItemIconMargin");
public static 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 class TagResourceKey

View File

@ -7,6 +7,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
namespace AtomUI.Controls;

View File

@ -122,6 +122,21 @@ internal class TabControlToken : AbstractControlDesignToken
/// </summary>
public Thickness ItemIconMargin { get; set; }
/// <summary>
/// 水平布局的菜单外边距
/// </summary>
public Thickness MenuIndicatorPaddingHorizontal { get; set; }
/// <summary>
/// 垂直布局的菜单外边距
/// </summary>
public Thickness MenuIndicatorPaddingVertical { get; set; }
/// <summary>
/// 滚动边缘的厚度
/// </summary>
public double MenuEdgeThickness { get; set; }
internal override void CalculateFromAlias()
{
base.CalculateFromAlias();
@ -138,6 +153,7 @@ internal class TabControlToken : AbstractControlDesignToken
bottom:_globalToken.PaddingXXS * 1.5,
left:_globalToken.Padding,
right:_globalToken.Padding);
TitleFontSize = fontToken.FontSize;
TitleFontSizeLG = fontToken.FontSizeLG;
TitleFontSizeSM = fontToken.FontSize;
@ -158,5 +174,10 @@ internal class TabControlToken : AbstractControlDesignToken
CardGutter = _globalToken.MarginXXS / 2;
ItemIconMargin = new Thickness(0, 0, _globalToken.MarginSM, 0);
MenuIndicatorPaddingHorizontal = new Thickness(_globalToken.PaddingXS, 0, 0, 0);
MenuIndicatorPaddingVertical = new Thickness(0, _globalToken.PaddingXS, 0, 0);
MenuEdgeThickness = 20;
}
}

View File

@ -0,0 +1,232 @@
using System.Globalization;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Converters;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Colors = Avalonia.Media.Colors;
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
{
private const int EdgeIndicatorZIndex = 1000;
#region
internal static readonly DirectProperty<TabScrollViewer, Dock> TabStripPlacementProperty =
AvaloniaProperty.RegisterDirect<TabScrollViewer, Dock>(nameof(TabStripPlacement),
o => o.TabStripPlacement,
(o, v) => o.TabStripPlacement = v);
internal static readonly DirectProperty<TabScrollViewer, IBrush?> EdgeShadowStartColorProperty =
AvaloniaProperty.RegisterDirect<TabScrollViewer, IBrush?>(nameof(EdgeShadowStartColor),
o => o.EdgeShadowStartColor,
(o, v) => o.EdgeShadowStartColor = v);
internal static readonly DirectProperty<TabScrollViewer, double> MenuEdgeThicknessProperty =
AvaloniaProperty.RegisterDirect<TabScrollViewer, double>(nameof(MenuEdgeThickness),
o => o.MenuEdgeThickness,
(o, v) => o.MenuEdgeThickness = v);
private Dock _tabStripPlacement;
internal Dock TabStripPlacement
{
get => _tabStripPlacement;
set => SetAndRaise(TabStripPlacementProperty, ref _tabStripPlacement, value);
}
private IBrush? _edgeShadowStartColor;
internal IBrush? EdgeShadowStartColor
{
get => _edgeShadowStartColor;
set => SetAndRaise(EdgeShadowStartColorProperty, ref _edgeShadowStartColor, value);
}
private double _menuEdgeThickness;
internal double MenuEdgeThickness
{
get => _menuEdgeThickness;
set => SetAndRaise(MenuEdgeThicknessProperty, ref _menuEdgeThickness, value);
}
#endregion
private IconButton? _menuIndicator;
private Border? _startEdgeIndicator;
private Border? _endEdgeIndicator;
static TabScrollViewer()
{
AffectsMeasure<TabScrollViewer>(TabStripPlacementProperty);
}
public TabScrollViewer()
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == TabStripPlacementProperty) {
if (Presenter is TabStripScrollContentPresenter tabStripScrollContentPresenter) {
tabStripScrollContentPresenter.TabStripPlacement = TabStripPlacement;
}
} else if (change.Property == VerticalScrollBarVisibilityProperty ||
change.Property == OffsetProperty ||
change.Property == ExtentProperty ||
change.Property == ViewportProperty) {
SetupIndicatorsVisibility();
}
}
private void SetupEdgeIndicatorSize()
{
if (Presenter is not null) {
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
if (_startEdgeIndicator is not null) {
_startEdgeIndicator.Height = Presenter.DesiredSize.Height;
_startEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, true);
}
if (_endEdgeIndicator is not null) {
_endEdgeIndicator.Height = Presenter.DesiredSize.Height;
_endEdgeIndicator.Margin = new Thickness(0, 0, _menuEdgeThickness, 0);
_endEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_endEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false);
}
} else {
if (_startEdgeIndicator is not null) {
_startEdgeIndicator.Width = Presenter.DesiredSize.Width;
_startEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false);
}
if (_endEdgeIndicator is not null) {
_endEdgeIndicator.Width = Presenter.DesiredSize.Width;
_endEdgeIndicator.Margin = new Thickness(0, _menuEdgeThickness, 0, 0);
_endEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_endEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false);
}
}
}
}
private LinearGradientBrush BuildEdgeIndicatorBrush(Dock placement, bool isStart)
{
var linearGradientBrush = new LinearGradientBrush()
{
GradientStops =
{
new GradientStop((_edgeShadowStartColor as SolidColorBrush)!.Color, 0),
new GradientStop(Colors.Transparent, 1),
}
};
if (placement == Dock.Top || placement == Dock.Bottom) {
if (isStart) {
linearGradientBrush.StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative);
linearGradientBrush.EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative);
} else {
linearGradientBrush.StartPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative);
linearGradientBrush.EndPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative);
}
} else {
if (isStart) {
linearGradientBrush.StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative);
linearGradientBrush.EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative);
} else {
linearGradientBrush.StartPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative);
linearGradientBrush.EndPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative);
}
}
return linearGradientBrush;
}
protected override bool RegisterContentPresenter(ContentPresenter presenter)
{
if (presenter is TabStripScrollContentPresenter tabStripScrollContentPresenter) {
tabStripScrollContentPresenter.TabStripPlacement = TabStripPlacement;
}
return base.RegisterContentPresenter(presenter);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_menuIndicator = e.NameScope.Find<IconButton>(TabScrollViewerTheme.ScrollMenuIndicatorPart);
_startEdgeIndicator = e.NameScope.Find<Border>(TabScrollViewerTheme.ScrollStartEdgeIndicatorPart);
_endEdgeIndicator = e.NameScope.Find<Border>(TabScrollViewerTheme.ScrollEndEdgeIndicatorPart);
TokenResourceBinder.CreateTokenBinding(this, EdgeShadowStartColorProperty, GlobalResourceKey.ColorFillSecondary);
TokenResourceBinder.CreateTokenBinding(this, MenuEdgeThicknessProperty, TabControlResourceKey.MenuEdgeThickness);
SetupIndicatorsVisibility();
}
protected override Size MeasureOverride(Size availableSize)
{
var size = base.MeasureOverride(availableSize);
SetupEdgeIndicatorSize();
return size;
}
private void SetupIndicatorsVisibility()
{
var args = new List<object?>();
object? scrollUpVisibility = default;
object? scrollDownVisibility = default;
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
args.Add(ScrollBarVisibility.Auto);
args.Add(Offset.X);
args.Add(Extent.Width);
args.Add(Viewport.Width);
scrollUpVisibility =
MenuScrollingVisibilityConverter.Instance.Convert(args, typeof(bool), 0d, CultureInfo.CurrentCulture);
scrollDownVisibility =
MenuScrollingVisibilityConverter.Instance.Convert(args, typeof(bool), 100d, CultureInfo.CurrentCulture);
} else {
args.Add(ScrollBarVisibility.Auto);
args.Add(Offset.Y);
args.Add(Extent.Height);
args.Add(Viewport.Height);
scrollUpVisibility =
MenuScrollingVisibilityConverter.Instance.Convert(args, typeof(bool), 0d, CultureInfo.CurrentCulture);
scrollDownVisibility =
MenuScrollingVisibilityConverter.Instance.Convert(args, typeof(bool), 100d, CultureInfo.CurrentCulture);
}
if (_startEdgeIndicator is not null &&
scrollUpVisibility is not null &&
scrollUpVisibility != AvaloniaProperty.UnsetValue) {
_startEdgeIndicator.IsVisible = (bool)scrollUpVisibility;
}
if (_endEdgeIndicator is not null &&
scrollDownVisibility is not null &&
scrollDownVisibility != AvaloniaProperty.UnsetValue) {
_endEdgeIndicator.IsVisible = (bool)scrollDownVisibility;
}
if (_menuIndicator is not null) {
var startEdgeVisible = _startEdgeIndicator?.IsVisible ?? false;
var endEdgeVisible = _endEdgeIndicator?.IsVisible ?? false;
_menuIndicator.IsVisible = startEdgeVisible || endEdgeVisible;
}
}
}

View File

@ -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 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<TabScrollViewer>((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<TabStripScrollContentPresenter>());
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<TabStripScrollContentPresenter>());
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<TabStripScrollContentPresenter>());
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<TabStripScrollContentPresenter>());
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);
}
}

View File

@ -0,0 +1,64 @@
using System.Reflection;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Rendering;
namespace AtomUI.Controls;
internal class TabStripScrollContentPresenter : ScrollContentPresenter, ICustomHitTest
{
internal Dock TabStripPlacement { get; set; } = Dock.Top;
private static readonly MethodInfo SnapOffsetMethodInfo;
static TabStripScrollContentPresenter()
{
SnapOffsetMethodInfo =
typeof(ScrollContentPresenter).GetMethod("SnapOffset", BindingFlags.Instance | BindingFlags.NonPublic)!;
}
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
if (Extent.Height > Viewport.Height || Extent.Width > Viewport.Width) {
var scrollable = Child as ILogicalScrollable;
var isLogical = scrollable?.IsLogicalScrollEnabled == true;
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);
}
if (Extent.Height > Viewport.Height) {
double height = isLogical ? scrollable!.ScrollSize.Height : 50;
y += -delta.Y * height;
y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height);
}
if (Extent.Width > Viewport.Width) {
double width = isLogical ? scrollable!.ScrollSize.Width : 50;
x += -delta.X * width;
x = Math.Max(x, 0);
x = Math.Min(x, Extent.Width - Viewport.Width);
}
Vector newOffset = (Vector)SnapOffsetMethodInfo.Invoke(this, new object?[] { new Vector(x, y), delta, true })!;
bool offsetChanged = newOffset != Offset;
SetCurrentValue(OffsetProperty, newOffset);
e.Handled = !IsScrollChainingEnabled || offsetChanged;
}
}
public bool HitTest(Point point)
{
return true;
}
}

View File

@ -16,6 +16,15 @@ internal class TabStripTheme : BaseTabStripTheme
public TabStripTheme() : base(typeof(TabStrip)) { }
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;
container.Child = tabScrollViewer;
}
private Panel CreateTabStripContentPanel(INameScope scope)
{
var layout = new Panel();
var itemsPresenter = new ItemsPresenter
@ -33,7 +42,7 @@ internal class TabStripTheme : BaseTabStripTheme
layout.Children.Add(itemsPresenter);
layout.Children.Add(border);
CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, TabStrip.ItemsPanelProperty);
container.Child = layout;
return layout;
}
protected override void BuildStyles()