feat: 完成timeline剩余样式和测试

This commit is contained in:
liuyushuai 2024-09-29 17:54:07 +08:00
parent dddab270a2
commit d15892fa44
8 changed files with 435 additions and 288 deletions

View File

@ -23,7 +23,7 @@
</atom:TimelineItem>
<atom:TimelineItem Color="red">
<TextBlock>
2024-10-01 Release of the 1.0 Preview Version
2024-10-01 Release of the 0.0.1 Preview Version
</TextBlock>
</atom:TimelineItem>
</atom:Timeline>
@ -60,27 +60,32 @@
</desktop:ShowCaseItem>
<desktop:ShowCaseItem Description="When the timeline is incomplete and ongoing, put a ghost node at last. Set pending as truthy value to enable displaying pending item. You can customize the pending content by passing a React Element. Meanwhile, pendingDot={a React Element} is used to customize the dot of the pending item. reverse={true} is used for reversing nodes." Title="Last node and Reversing">
<atom:Timeline Pending="Recording..." Reverse="True">
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated. 1
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-08-12">
<TextBlock>
2024-01-01 AtomUI Officially Initiated. 2
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-10-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated. 3
</TextBlock>
</atom:TimelineItem>
</atom:Timeline>
<StackPanel>
<atom:Timeline x:Name="ReverseTimeline" Pending="Recording..." Reverse="True">
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated. 1
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-08-12">
<TextBlock>
2024-01-01 AtomUI Officially Initiated. 2
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-10-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated. 3
</TextBlock>
</atom:TimelineItem>
</atom:Timeline>
<DockPanel>
<atom:Button ButtonType="Primary" x:Name="ReverseButton">Toggle Reverse</atom:Button>
</DockPanel>
</StackPanel>
</desktop:ShowCaseItem>
<desktop:ShowCaseItem Description="Alternate timeline." Title="Alternate">
<atom:Timeline Mode="alternate">
<atom:Timeline Mode="Alternate">
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
@ -96,7 +101,7 @@
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem DotIcon="{atom:IconProvider Kind=ClockCircleOutlined, NormalFilledColor=LightBlue}" Label="2024-01-01">
<atom:TimelineItem DotIcon="{atom:IconProvider Kind=ClockCircleOutlined, NormalFilledColor=Red}" Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
@ -105,32 +110,39 @@
</desktop:ShowCaseItem>
<desktop:ShowCaseItem Description="Use label show time alone." Title="Label">
<atom:Timeline Mode="alternate">
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem>
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
</atom:Timeline>
<StackPanel>
<WrapPanel Orientation="Horizontal" Margin="0, 0, 0, 20">
<atom:RadioButton x:Name="ModeLeft" IsChecked="True">Left</atom:RadioButton>
<atom:RadioButton x:Name="ModeRight">Right</atom:RadioButton>
<atom:RadioButton x:Name="ModeAlternate">Alternate</atom:RadioButton>
</WrapPanel>
<atom:Timeline Mode="Left" x:Name="LabelTimeline">
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem>
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
<atom:TimelineItem Label="2024-01-01">
<TextBlock>
2024-01-01 AtomUI Officially Initiated
</TextBlock>
</atom:TimelineItem>
</atom:Timeline>
</StackPanel>
</desktop:ShowCaseItem>
<desktop:ShowCaseItem Description="Right alternate timeline." Title="Right alternate">
<atom:Timeline Mode="right">
<atom:Timeline Mode="Right">
<atom:TimelineItem>
<TextBlock>
2024-01-01 AtomUI Officially Initiated

View File

@ -1,11 +1,49 @@
using AtomUI.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using RadioButton = Avalonia.Controls.RadioButton;
namespace AtomUI.Demo.Desktop.ShowCase;
public partial class TimelineShowCase : UserControl
{
public TimelineShowCase()
{
InitializeComponent();
ModeLeft.Checked += ModeChecked;
ModeRight.Checked += ModeChecked;
ModeAlternate.Checked += ModeChecked;
ReverseButton.Click += ReverseButtonClick;
}
private void ReverseButtonClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
ReverseTimeline.Reverse = !ReverseTimeline.Reverse;
}
private void ModeChecked(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (sender is RadioButton radioButton)
{
if (radioButton.Content?.ToString() == "Left")
{
LabelTimeline.Mode = TimeLineMode.Left;
}
else if (radioButton.Content?.ToString() == "Right")
{
LabelTimeline.Mode = TimeLineMode.Right;
}
else if (radioButton.Content?.ToString() == "Alternate")
{
LabelTimeline.Mode = TimeLineMode.Alternate;
}
}
}
}

View File

@ -586,6 +586,8 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey LeftMargin = new TokenResourceKey("Timeline.LeftMargin", "AtomUI.Token");
public static readonly TokenResourceKey LastItemContentMinHeight = new TokenResourceKey("Timeline.LastItemContentMinHeight", "AtomUI.Token");
public static readonly TokenResourceKey FontSize = new TokenResourceKey("Timeline.FontSize", "AtomUI.Token");
public static readonly TokenResourceKey ItemHeadSize = new TokenResourceKey("Timeline.ItemHeadSize", "AtomUI.Token");
public static readonly TokenResourceKey CustomHeadSize = new TokenResourceKey("Timeline.CustomHeadSize", "AtomUI.Token");
}
public static class TimePickerTokenResourceKey

View File

@ -11,12 +11,19 @@ using Avalonia.Layout;
namespace AtomUI.Controls;
public enum TimeLineMode
{
Left,
Right,
Alternate
}
public class Timeline : ItemsControl
{
#region
public static readonly StyledProperty<string> ModeProperty =
AvaloniaProperty.Register<Timeline, string>(nameof(Mode), "left");
public static readonly StyledProperty<TimeLineMode> ModeProperty =
AvaloniaProperty.Register<Timeline, TimeLineMode>(nameof(Mode), TimeLineMode.Left);
public static readonly StyledProperty<string> PendingProperty =
AvaloniaProperty.Register<Timeline, string>(nameof(Pending), "");
@ -27,7 +34,7 @@ public class Timeline : ItemsControl
public static readonly StyledProperty<PathIcon?> PendingIconProperty =
AvaloniaProperty.Register<Alert, PathIcon?>(nameof(PendingIcon));
public string Mode
public TimeLineMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
@ -53,53 +60,10 @@ public class Timeline : ItemsControl
#endregion
public Timeline()
{
if (Reverse)
{
OnReversePropertyChanged();
}
}
protected override void OnInitialized()
{
base.OnInitialized();
if (!String.IsNullOrEmpty(Pending))
{
var item = new TimelineItem();
var textBlock = new TextBlock();
if (PendingIcon is null)
{
PendingIcon = new PathIcon
{
Kind = "LoadingOutlined",
Width = 10,
Height = 10,
LoadingAnimation = IconAnimation.Spin,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Center,
};
}
item.DotIcon = PendingIcon;
item.IsPending = true;
item.Content = textBlock;
BindUtils.RelayBind(this, PendingProperty, textBlock, TextBlock.TextProperty);
Items.Add(item);
}
if (Reverse)
{
OnReversePropertyChanged();
}
}
private TimelineItem? _pendingItem;
static Timeline()
{
ReverseProperty.Changed.AddClassHandler<Timeline>((x, e) => x.OnReversePropertyChanged());
}
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
@ -117,23 +81,9 @@ public class Timeline : ItemsControl
base.PrepareContainerForItemOverride(element, item, index);
if (element is TimelineItem timelineItem)
{
timelineItem.Index = index;
timelineItem.Mode = Mode;
timelineItem.IsLast = Items.Count - 1 == index;
timelineItem.IsFirst = index == 0;
BindUtils.RelayBind(this, ModeProperty, timelineItem, TimelineItem.ModeProperty);
BindUtils.RelayBind(this, ReverseProperty, timelineItem, TimelineItem.ReverseProperty);
foreach (var child in Items)
{
if (child is TimelineItem otherItem)
{
if (!string.IsNullOrEmpty(otherItem.Label))
{
timelineItem.HasLabel = true;
break;
}
}
}
BindUtils.RelayBind(this, ItemCountProperty, timelineItem, TimelineItem.CountProperty);
}
}
@ -141,7 +91,10 @@ public class Timeline : ItemsControl
{
base.OnApplyTemplate(e);
TokenResourceBinder.CreateGlobalResourceBinding(this, BorderThicknessProperty,
OnReversePropertyChanged();
addPendingItem();
TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty,
GlobalTokenResourceKey.BorderThickness,
BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this));
@ -154,6 +107,73 @@ public class Timeline : ItemsControl
{
OnReversePropertyChanged();
}
if (change.Property == ItemCountProperty && VisualRoot is not null)
{
OnItemCountPropertyChanged();
}
if (change.Property == PendingProperty && VisualRoot is not null)
{
OnPendingPropertyChanged();
}
}
private void OnPendingPropertyChanged()
{
foreach (var item in Items)
{
if (item is TimelineItem timelineItem && timelineItem.IsPending)
{
Items.Remove(item);
break;
}
}
addPendingItem();
}
private void addPendingItem()
{
if (!String.IsNullOrEmpty(Pending))
{
if (_pendingItem is null)
{
_pendingItem = new TimelineItem();
var textBlock = new TextBlock();
if (PendingIcon is null)
{
PendingIcon = new PathIcon
{
Kind = "LoadingOutlined",
Width = 10,
Height = 10,
LoadingAnimation = IconAnimation.Spin,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Center,
};
}
_pendingItem.DotIcon = PendingIcon;
_pendingItem.IsPending = true;
_pendingItem.Content = textBlock;
BindUtils.RelayBind(this, PendingProperty, textBlock, TextBlock.TextProperty);
}
if (Reverse)
{
Items.Insert(0, _pendingItem);
}
else
{
Items.Add(_pendingItem);
}
}
}
private void OnItemCountPropertyChanged()
{
}
private void OnReversePropertyChanged()
@ -164,6 +184,18 @@ public class Timeline : ItemsControl
foreach (var item in items)
{
Items.Add(item);
if (item is TimelineItem timelineItem)
{
timelineItem.Index = Items.IndexOf(item);
}
}
foreach (var item in items)
{
if (item is TimelineItem timelineItem)
{
timelineItem.Index = Items.IndexOf(item);
}
}
}
}

View File

@ -3,17 +3,19 @@ using AtomUI.Utils;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Layout;
using Avalonia.Media;
namespace AtomUI.Controls;
public class TimelineItem : ContentControl
{
internal const string PendingNode = ":pending";
internal const string ContentLeft = ":ContentLeft";
internal const string LabelLeft = ":labelLeft";
internal const string PendingNodePC = ":pending";
internal const string ContentLeftPC = ":ContentLeft";
internal const string LabelLeftPC = ":labelLeft";
#region
@ -47,11 +49,15 @@ public class TimelineItem : ContentControl
#endregion
#region
internal static readonly StyledProperty<int> IndexProperty =
AvaloniaProperty.Register<TimelineItem, int>(nameof(Index), 0);
internal static readonly StyledProperty<string> ModeProperty =
AvaloniaProperty.Register<TimelineItem, string>(nameof(Mode), "left");
internal static readonly StyledProperty<int> CountProperty =
AvaloniaProperty.Register<TimelineItem, int>(nameof(Count), 0);
internal static readonly StyledProperty<TimeLineMode> ModeProperty =
AvaloniaProperty.Register<TimelineItem, TimeLineMode>(nameof(Mode), TimeLineMode.Left);
internal static readonly StyledProperty<bool> HasLabelProperty =
AvaloniaProperty.Register<TimelineItem, bool>(nameof(HasLabel), false);
@ -59,22 +65,32 @@ public class TimelineItem : ContentControl
internal static readonly StyledProperty<bool> IsLastProperty =
AvaloniaProperty.Register<TimelineItem, bool>(nameof(IsLast), false);
internal static readonly StyledProperty<bool> IsFirstProperty =
AvaloniaProperty.Register<TimelineItem, bool>(nameof(IsFirst), false);
internal static readonly StyledProperty<bool> ReverseProperty =
AvaloniaProperty.Register<TimelineItem, bool>(nameof(Reverse), false);
internal static readonly StyledProperty<bool> IsPendingProperty =
AvaloniaProperty.Register<TimelineItem, bool>(nameof(IsPending), false);
internal static readonly StyledProperty<HorizontalAlignment> ContentTextAlignProperty =
AvaloniaProperty.Register<TimelineItem, HorizontalAlignment>(nameof(ContentTextAlign),
HorizontalAlignment.Left);
internal static readonly StyledProperty<HorizontalAlignment> LabelTextAlignProperty =
AvaloniaProperty.Register<TimelineItem, HorizontalAlignment>(nameof(LabelTextAlign), HorizontalAlignment.Left);
internal int Index
{
get => GetValue(IndexProperty);
set => SetValue(IndexProperty, value);
}
internal string Mode
internal int Count
{
get => GetValue(CountProperty);
set => SetValue(CountProperty, value);
}
internal TimeLineMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
@ -86,12 +102,6 @@ public class TimelineItem : ContentControl
set => SetValue(HasLabelProperty, value);
}
internal bool IsFirst
{
get => GetValue(IsFirstProperty);
set => SetValue(IsFirstProperty, value);
}
internal bool IsLast
{
get => GetValue(IsLastProperty);
@ -109,23 +119,40 @@ public class TimelineItem : ContentControl
get => GetValue(IsPendingProperty);
set => SetValue(IsPendingProperty, value);
}
internal HorizontalAlignment ContentTextAlign
{
get => GetValue(ContentTextAlignProperty);
set => SetValue(ContentTextAlignProperty, value);
}
internal HorizontalAlignment LabelTextAlign
{
get => GetValue(LabelTextAlignProperty);
set => SetValue(LabelTextAlignProperty, value);
}
#endregion
protected internal int LabelIndex = 0;
protected internal int SplitIndex = 1;
protected internal int ContentIndex = 2;
protected internal HorizontalAlignment LabelTextAlign = HorizontalAlignment.Right;
protected internal HorizontalAlignment ContentTextAlign = HorizontalAlignment.Left;
private Grid? _gridContainer;
private DockPanel? _splitPanel;
private TextBlock? _labelBlock;
private ContentPresenter? _itemsContentPresenter;
private Border? _splitHeadPart;
private PathIcon? _dotPart;
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
CalculateIndex();
CalculateOther();
UpdatePseudoClasses();
if (change.Property == ModeProperty || change.Property == CountProperty || change.Property == IndexProperty)
{
UpdateAll();
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@ -137,72 +164,120 @@ public class TimelineItem : ContentControl
private void HandleTemplateApplied(INameScope scope)
{
_splitPanel = scope.Find<DockPanel>(TimelineItemTheme.SplitPanelPart);
UpdatePseudoClasses();
_gridContainer = scope.Find<Grid>(TimelineItemTheme.GridPart);
_splitPanel = scope.Find<DockPanel>(TimelineItemTheme.SplitPanelPart);
_labelBlock = scope.Find<TextBlock>(TimelineItemTheme.LabelPart);
_itemsContentPresenter = scope.Find<ContentPresenter>(TimelineItemTheme.ItemsContentPresenterPart);
_splitHeadPart = scope.Find<Border>(TimelineItemTheme.SplitHeadPart);
_dotPart = scope.Find<PathIcon>(TimelineItemTheme.DotPart);
UpdateAll();
}
protected void UpdateAll()
{
CalculateIndex();
SetupShowInfo();
UpdatePseudoClasses();
}
protected void UpdatePseudoClasses()
{
PseudoClasses.Set(PendingNode, IsPending);
PseudoClasses.Set(ContentLeft, ContentIndex == 0);
PseudoClasses.Set(LabelLeft, LabelIndex == 0);
PseudoClasses.Set(PendingNodePC, IsPending);
PseudoClasses.Set(ContentLeftPC, ContentIndex == 0);
PseudoClasses.Set(LabelLeftPC, LabelIndex == 0);
}
private void CalculateIndex()
{
LabelIndex = 0;
if (VisualRoot is null || _gridContainer is null)
{
return;
}
if (Parent is Timeline timeline)
{
Index = timeline.Items.IndexOf(this);
IsLast = Index == timeline.Items.Count - 1;
HasLabel = false;
foreach (var child in timeline.Items)
{
if (child is TimelineItem item)
{
if (!string.IsNullOrEmpty(item.Label))
{
HasLabel = true;
break;
}
}
}
}
SplitIndex = 1;
ContentIndex = 2;
LabelTextAlign = HorizontalAlignment.Right;
ContentTextAlign = HorizontalAlignment.Left;
if (Mode == "right" || (Mode == "alternate" && Index % 2 == 1))
if (Mode == TimeLineMode.Right || (Mode == TimeLineMode.Alternate && Index % 2 == 1))
{
LabelIndex = 2;
ContentIndex = 0;
LabelTextAlign = HorizontalAlignment.Left;
ContentTextAlign = HorizontalAlignment.Right;
}
if (!HasLabel && Mode == "left")
else
{
SplitIndex = 0;
ContentIndex = 1;
LabelIndex = 0;
ContentIndex = 2;
LabelTextAlign = HorizontalAlignment.Right;
ContentTextAlign = HorizontalAlignment.Left;
}
if (!HasLabel && Mode == "right")
_gridContainer.ColumnDefinitions[0].Width = GridLength.Star;
_gridContainer.ColumnDefinitions[2].Width = GridLength.Star;
if (!HasLabel)
{
SplitIndex = 1;
ContentIndex = 0;
ContentTextAlign = HorizontalAlignment.Right;
if (Mode == TimeLineMode.Left)
{
LabelIndex = 0;
ContentIndex = 2;
ContentTextAlign = HorizontalAlignment.Left;
}
if (Mode == TimeLineMode.Right)
{
LabelIndex = 2;
ContentIndex = 0;
ContentTextAlign = HorizontalAlignment.Right;
}
_gridContainer.ColumnDefinitions[LabelIndex].Width = new GridLength(0);
}
}
private void CalculateOther()
{
}
private void SetupShowInfo()
{
if (_splitPanel is null)
if (_splitPanel is null || _itemsContentPresenter is null || _labelBlock is null || _gridContainer is null)
{
return;
}
Grid.SetColumn(_labelBlock, LabelIndex);
Grid.SetColumn(_itemsContentPresenter, ContentIndex);
Grid.SetColumn(_splitPanel, SplitIndex);
var dot = _splitPanel.Children[0];
var border = _splitPanel.Children[1] as Border;
var rect = border?.Child as Rectangle;
var isPendingItem = false;
MinHeight = 0;
if (IsLast && rect is not null)
if (Parent is Timeline timeline && !String.IsNullOrEmpty(timeline.Pending) && rect is not null &&
border is not null)
{
rect.IsVisible = false;
}
rect.StrokeDashArray = null;
if (Parent is Timeline timeline && !String.IsNullOrEmpty(timeline.Pending) && rect is not null && border is not null)
{
isPendingItem = Reverse && Index == 0 || !Reverse && timeline.ItemCount - 2 == Index;
if (isPendingItem)
{
@ -210,11 +285,77 @@ public class TimelineItem : ContentControl
}
}
if (rect is not null)
{
rect.IsVisible = !IsLast;
}
if (IsLast || isPendingItem)
{
TokenResourceBinder.CreateGlobalTokenBinding(this, Layoutable.MinHeightProperty,
TimelineTokenResourceKey.LastItemContentMinHeight);
}
if (Color.StartsWith("#"))
{
try
{
var color = Avalonia.Media.Color.Parse(Color);
var brush = new SolidColorBrush(color);
if (_dotPart is not null && _dotPart.NormalFilledBrush is null)
{
_dotPart.NormalFilledBrush = brush;
}
if (_splitHeadPart is not null)
{
_splitHeadPart.BorderBrush = brush;
}
}
catch (Exception)
{
if (_dotPart is not null && _dotPart.NormalFilledBrush is null)
{
TokenResourceBinder.CreateGlobalTokenBinding(_dotPart, PathIcon.NormalFilledBrushProperty,
GlobalTokenResourceKey.ColorPrimary);
}
if (_splitHeadPart is not null)
{
TokenResourceBinder.CreateGlobalTokenBinding(_splitHeadPart, Border.BorderBrushProperty,
GlobalTokenResourceKey.ColorPrimary);
}
}
}
else
{
var tokenText = GlobalTokenResourceKey.ColorSuccess;
switch (Color)
{
case "blue":
tokenText = GlobalTokenResourceKey.ColorPrimary;
break;
case "green":
tokenText = GlobalTokenResourceKey.ColorSuccess;
break;
case "red":
tokenText = GlobalTokenResourceKey.ColorError;
break;
case "gray":
tokenText = GlobalTokenResourceKey.ColorTextDisabled;
break;
}
if (_dotPart is not null && _dotPart.NormalFilledBrush is null)
{
TokenResourceBinder.CreateGlobalTokenBinding(_dotPart, PathIcon.NormalFilledBrushProperty,
tokenText);
}
if (_splitHeadPart is not null)
{
TokenResourceBinder.CreateGlobalTokenBinding(_splitHeadPart, Border.BorderBrushProperty,
tokenText);
}
}
}
}

View File

@ -29,37 +29,39 @@ internal class TimelineItemTheme : BaseControlTheme
{
}
protected TimelineItemTheme(Type targetType) : base(targetType)
{
}
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<TimelineItem>((timelineItem, scope) =>
{
var columnDefinition = CalculateGridColumn(timelineItem);
var grid = new Grid()
{
Name = GridPart,
ColumnDefinitions = columnDefinition,
Name = GridPart,
ColumnDefinitions = new ColumnDefinitions()
{
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(new GridLength(10)),
new ColumnDefinition(GridLength.Star)
},
RowDefinitions =
{
new RowDefinition(GridLength.Star),
new RowDefinition(GridLength.Star),
},
};
grid.RegisterInNameScope(scope);
var labelBlock = new TextBlock()
{
Name = LabelPart,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = timelineItem.LabelTextAlign,
Name = LabelPart,
VerticalAlignment = VerticalAlignment.Top,
};
labelBlock.RegisterInNameScope(scope);
CreateTemplateParentBinding(labelBlock, TextBlock.TextProperty, TimelineItem.LabelProperty);
CreateTemplateParentBinding(labelBlock, TextBlock.IsVisibleProperty, TimelineItem.HasLabelProperty);
CreateTemplateParentBinding(labelBlock, Visual.IsVisibleProperty, TimelineItem.HasLabelProperty);
CreateTemplateParentBinding(labelBlock, Layoutable.HorizontalAlignmentProperty,
TimelineItem.LabelTextAlignProperty);
Grid.SetColumn(labelBlock, timelineItem.LabelIndex);
grid.Children.Add(labelBlock);
var splitPanel = new DockPanel()
@ -91,19 +93,19 @@ internal class TimelineItemTheme : BaseControlTheme
border.Child = verticalDashedLine;
splitPanel.Children.Add(border);
Grid.SetColumn(splitPanel, timelineItem.SplitIndex);
grid.Children.Add(splitPanel);
var contentPresenter = new ContentPresenter()
{
Name = ItemsContentPresenterPart,
HorizontalAlignment = timelineItem.ContentTextAlign,
Name = ItemsContentPresenterPart,
};
contentPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty,
ContentControl.ContentProperty);
CreateTemplateParentBinding(contentPresenter, Layoutable.HorizontalAlignmentProperty,
TimelineItem.ContentTextAlignProperty);
Grid.SetColumn(contentPresenter, timelineItem.ContentIndex);
grid.Children.Add(contentPresenter);
return grid;
@ -114,9 +116,9 @@ internal class TimelineItemTheme : BaseControlTheme
{
if (timelineItem.DotIcon is not null)
{
timelineItem.DotIcon.Width = 10;
timelineItem.DotIcon.Height = 10;
timelineItem.DotIcon.Name = DotPart;
timelineItem.DotIcon.Width = 10;
timelineItem.DotIcon.Height = 10;
timelineItem.DotIcon.Name = DotPart;
timelineItem.DotIcon.RegisterInNameScope(scope);
DockPanel.SetDock(timelineItem.DotIcon, Dock.Top);
panel.Children.Add(timelineItem.DotIcon);
@ -131,97 +133,10 @@ internal class TimelineItemTheme : BaseControlTheme
BorderThickness = new Thickness(3),
Name = SplitHeadPart,
};
splitHead.RegisterInNameScope(scope);
DockPanel.SetDock(splitHead, Dock.Top);
panel.Children.Add(splitHead);
}
var dotBorderStyle = new Style(selector => selector.Nesting().Template().Name(SplitHeadPart));
var dotIconStyle = new Style(selector =>
selector.Nesting().Not(x => x.PropertyEquals(TimelineItem.DotIconProperty, null)).Template().Name(DotPart));
if (timelineItem.Color.StartsWith("#"))
{
try
{
var color = Color.Parse(timelineItem.Color);
var brush = new SolidColorBrush(color);
dotBorderStyle.Add(ContentPresenter.BorderBrushProperty, brush);
dotIconStyle.Add(PathIcon.NormalFilledBrushProperty, brush);
}
catch (Exception)
{
dotBorderStyle.Add(ContentPresenter.BorderBrushProperty,
GlobalTokenResourceKey.ColorPrimary);
}
}
else
{
switch (timelineItem.Color)
{
case "blue":
dotBorderStyle.Add(ContentPresenter.BorderBrushProperty,
GlobalTokenResourceKey.ColorPrimary);
dotIconStyle.Add(PathIcon.NormalFilledBrushProperty, GlobalTokenResourceKey.ColorPrimary);
break;
case "green":
dotBorderStyle.Add(ContentPresenter.BorderBrushProperty,
GlobalTokenResourceKey.ColorSuccess);
dotIconStyle.Add(PathIcon.NormalFilledBrushProperty, GlobalTokenResourceKey.ColorSuccess);
break;
case "red":
dotBorderStyle.Add(ContentPresenter.BorderBrushProperty,
GlobalTokenResourceKey.ColorError);
dotIconStyle.Add(PathIcon.NormalFilledBrushProperty, GlobalTokenResourceKey.ColorError);
break;
case "gray":
dotBorderStyle.Add(ContentPresenter.BorderBrushProperty,
GlobalTokenResourceKey.ColorTextDisabled);
dotIconStyle.Add(PathIcon.NormalFilledBrushProperty, GlobalTokenResourceKey.ColorTextDisabled);
break;
default:
dotBorderStyle.Add(ContentPresenter.BorderBrushProperty,
GlobalTokenResourceKey.ColorPrimary);
dotIconStyle.Add(PathIcon.NormalFilledBrushProperty, GlobalTokenResourceKey.ColorPrimary);
break;
}
}
dotBorderStyle.Add(ContentPresenter.BackgroundProperty, TimelineTokenResourceKey.DotBg);
Add(dotBorderStyle);
if (timelineItem.DotIcon?.NormalFilledBrush is null)
{
Add(dotIconStyle);
}
}
private ColumnDefinitions CalculateGridColumn(TimelineItem timelineItem)
{
if (timelineItem.HasLabel || timelineItem.Mode == "alternate")
{
return new ColumnDefinitions()
{
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(new GridLength(10)),
new ColumnDefinition(GridLength.Star)
};
}
if (timelineItem.LabelIndex == 0)
{
return new ColumnDefinitions()
{
new ColumnDefinition(new GridLength(10)),
new ColumnDefinition(GridLength.Star)
};
}
else
{
return new ColumnDefinitions()
{
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(new GridLength(10))
};
}
}
protected override void BuildStyles()
@ -252,8 +167,8 @@ internal class TimelineItemTheme : BaseControlTheme
new Style(selector => selector.Nesting().Template().Name(ItemsContentPresenterPart));
contentPresenterRightStyle.Add(Layoutable.MarginProperty, TimelineTokenResourceKey.LeftMargin);
var contentLeftStyle = new Style(selector => selector.Nesting().Class(TimelineItem.ContentLeft));
var contentRightStyle = new Style(selector => selector.Nesting().Not(x => x.Class(TimelineItem.ContentLeft)));
var contentLeftStyle = new Style(selector => selector.Nesting().Class(TimelineItem.ContentLeftPC));
var contentRightStyle = new Style(selector => selector.Nesting().Not(x => x.Class(TimelineItem.ContentLeftPC)));
contentLeftStyle.Add(contentPresenterLeftStyle);
contentRightStyle.Add(contentPresenterRightStyle);
@ -267,9 +182,9 @@ internal class TimelineItemTheme : BaseControlTheme
labelStyle.Add(TextBlock.FontSizeProperty, TimelineTokenResourceKey.FontSize);
var labelLeftStyle = new Style(selector =>
selector.Nesting().Class(TimelineItem.LabelLeft).Template().Name(LabelPart));
selector.Nesting().Class(TimelineItem.LabelLeftPC).Template().Name(LabelPart));
var labelRightStyle = new Style(selector =>
selector.Nesting().Not(x => x.Class(TimelineItem.LabelLeft)).Template().Name(LabelPart));
selector.Nesting().Not(x => x.Class(TimelineItem.LabelLeftPC)).Template().Name(LabelPart));
labelLeftStyle.Add(TextBlock.PaddingProperty, TimelineTokenResourceKey.RightMargin);
labelRightStyle.Add(TextBlock.PaddingProperty, TimelineTokenResourceKey.LeftMargin);

View File

@ -4,7 +4,6 @@ using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
@ -16,8 +15,7 @@ internal class TimelineTheme : BaseControlTheme
public const string ScrollViewerPart = "PART_ScrollViewer";
public const string ItemsPresenterPart = "PART_ItemsPresenter";
public TimelineTheme() : this(typeof(Timeline)) { }
protected TimelineTheme(Type targetType) : base(targetType) { }
public TimelineTheme() : base(typeof(Timeline)) { }
protected override IControlTemplate BuildControlTemplate()
{

View File

@ -1,4 +1,3 @@
using AtomUI.Theme.Styling;
using AtomUI.Theme.TokenSystem;
using Avalonia;
using Avalonia.Media;
@ -71,6 +70,15 @@ internal class TimelineToken : AbstractControlDesignToken
/// </summary>
public double FontSize { get; set; }
/// <summary>
/// item head size
/// </summary>
public double ItemHeadSize { get; set; }
/// <summary>
/// custom head size
/// </summary>
public double CustomHeadSize { get; set; }
internal override void CalculateFromAlias()
{
@ -89,8 +97,9 @@ internal class TimelineToken : AbstractControlDesignToken
RightMargin = new Thickness(0, 0, _globalToken.MarginSM, 0);
LastItemContentMinHeight = _globalToken.HeightToken.ControlHeightLG * 1.2;
FontSize = _globalToken.FontToken.FontSize;
FontSize = _globalToken.FontToken.FontSize;
ItemHeadSize = 10;
CustomHeadSize = _globalToken.FontToken.FontSize;
}
}