From 0c4b6edfc22bc98b77bba489e4b9ec3a927fe70b Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 27 Dec 2023 00:58:50 +0800 Subject: [PATCH 01/14] feat: start to refactor. --- demo/Ursa.Demo/Pages/IntroductionDemo.axaml | 2 + demo/Ursa.Demo/Pages/TimelineDemo.axaml | 50 ++------- .../ViewModels/TimelineDemoViewModel.cs | 24 +---- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 14 +-- src/Ursa/Controls/Timeline/Timeline.cs | 92 ++++++++++++---- src/Ursa/Controls/Timeline/TimelineItem.cs | 100 ++++-------------- 6 files changed, 111 insertions(+), 171 deletions(-) diff --git a/demo/Ursa.Demo/Pages/IntroductionDemo.axaml b/demo/Ursa.Demo/Pages/IntroductionDemo.axaml index 560ab49..4fbf945 100644 --- a/demo/Ursa.Demo/Pages/IntroductionDemo.axaml +++ b/demo/Ursa.Demo/Pages/IntroductionDemo.axaml @@ -144,6 +144,7 @@ IPAddress="{Binding Address}" /> + diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index b217fad..ad01c4f 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -8,51 +8,19 @@ xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels" d:DesignHeight="450" d:DesignWidth="800" - x:CompileBindings="False" x:DataType="viewModels:TimelineDemoViewModel" + x:CompileBindings="True" mc:Ignorable="d"> - - - - - - - - + - - - - - - - - - - - - + + + + diff --git a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs index fc9e819..ece4543 100644 --- a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs @@ -13,7 +13,7 @@ public class TimelineDemoViewModel: ViewModelBase Time = DateTime.Now, TimeFormat = "yyyy-MM-dd HH:mm:ss", Description = "Item 1", - Content = "First", + Header = "审核中", ItemType = TimelineItemType.Success, }, new() @@ -21,7 +21,7 @@ public class TimelineDemoViewModel: ViewModelBase Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 2", - Content = "Content 2", + Header = "发布成功", ItemType = TimelineItemType.Success, }, new() @@ -29,23 +29,9 @@ public class TimelineDemoViewModel: ViewModelBase Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 3", - Content = "Content 3", + Header = "审核失败", ItemType = TimelineItemType.Ongoing, - }, - new() - { - Time = DateTime.Now, - TimeFormat = "HH:mm:ss", - Description = "Item 4", - Content = "Content 4" - }, - new() - { - Time = DateTime.Now, - TimeFormat = "HH:mm:ss", - Description = "Item 5", - Content = "Content 5" - }, + } }; } @@ -54,6 +40,6 @@ public class TimelineItemViewModel: ObservableObject public DateTime Time { get; set; } public string? TimeFormat { get; set; } public string? Description { get; set; } - public string? Content { get; set; } + public string? Header { get; set; } public TimelineItemType ItemType { get; set; } } \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index b8f62fa..759be18 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -6,9 +6,9 @@ - - - + + + @@ -81,13 +81,9 @@ Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" + Content="{TemplateBinding Header}" + ContentTemplate="{TemplateBinding HeaderTemplate}" Foreground="Gray"> - - - - - - IconMemberBindingProperty = AvaloniaProperty.Register( + nameof(IconMemberBinding)); + + [AssignBinding] + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IBinding? IconMemberBinding + { + get => GetValue(IconMemberBindingProperty); + set => SetValue(IconMemberBindingProperty, value); + } + + public static readonly StyledProperty HeaderMemberBindingProperty = AvaloniaProperty.Register( + nameof(HeaderMemberBinding)); + + [AssignBinding] + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IBinding? HeaderMemberBinding + { + get => GetValue(HeaderMemberBindingProperty); + set => SetValue(HeaderMemberBindingProperty, value); + } + + public static readonly StyledProperty DescriptionMemberBindingProperty = AvaloniaProperty.Register( + nameof(DescriptionMemberBinding)); - public static readonly StyledProperty ItemDescriptionTemplateProperty = AvaloniaProperty.Register( - nameof(ItemDescriptionTemplate)); - - public IDataTemplate? ItemDescriptionTemplate + [AssignBinding] + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IBinding? DescriptionMemberBinding { - get => GetValue(ItemDescriptionTemplateProperty); - set => SetValue(ItemDescriptionTemplateProperty, value); + get => GetValue(DescriptionMemberBindingProperty); + set => SetValue(DescriptionMemberBindingProperty, value); + } + + + public static readonly StyledProperty IconTemplateProperty = AvaloniaProperty.Register( + nameof(IconTemplate)); + + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IDataTemplate? IconTemplate + { + get => GetValue(IconTemplateProperty); + set => SetValue(IconTemplateProperty, value); } - public Timeline() + public static readonly StyledProperty DescriptionTemplateProperty = AvaloniaProperty.Register( + nameof(DescriptionTemplate)); + + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IDataTemplate? DescriptionTemplate { - ItemsView.CollectionChanged+=ItemsViewOnCollectionChanged; + get => GetValue(DescriptionTemplateProperty); + set => SetValue(DescriptionTemplateProperty, value); } - private void ItemsViewOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { - RefreshTimelineItems(); + recycleKey = null; + return item is not TimelineItem; } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { - base.OnPropertyChanged(change); - RefreshTimelineItems(); + if (item is TimelineItem t) return t; + return new TimelineItem(); } - private void RefreshTimelineItems() + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) { - for (int i = 0; i < this.LogicalChildren.Count; i++) + base.PrepareContainerForItemOverride(container, item, index); + if (container is TimelineItem t) { - if (this.LogicalChildren[i] is TimelineItem t) + if (IconMemberBinding != null) { - t.SetIndex(i == 0, i == this.LogicalChildren.Count - 1); + t.Bind(TimelineItem.IconProperty, IconMemberBinding); } - else if (this.LogicalChildren[i] is ContentPresenter { Child: TimelineItem t2 }) + if (HeaderMemberBinding != null) { - t2.SetIndex(i == 0, i == this.LogicalChildren.Count - 1); + t.Bind(HeaderedContentControl.HeaderProperty, HeaderMemberBinding); } + if (DescriptionMemberBinding != null) + { + t.Bind(ContentControl.ContentProperty, DescriptionMemberBinding); + } + t.SetCurrentValue(TimelineItem.IconTemplateProperty, IconTemplate); + t.SetCurrentValue(HeaderedContentControl.HeaderTemplateProperty, ItemTemplate); + t.SetCurrentValue(ContentControl.ContentTemplateProperty, DescriptionTemplate); } + } - } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 0958a31..9fe8af4 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -1,101 +1,41 @@ -using System.Globalization; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Media; namespace Ursa.Controls; -[PseudoClasses(PC_First, PC_Last, PC_Default, PC_Ongoing, PC_Success, PC_Warning, PC_Error, PC_None)] -public class TimelineItem: ContentControl +public class TimelineItem: HeaderedContentControl { - private const string PC_First = ":first"; - private const string PC_Last = ":last"; - private const string PC_Default = ":default"; - private const string PC_Ongoing = ":ongoing"; - private const string PC_Success = ":success"; - private const string PC_Warning = ":warning"; - private const string PC_Error = ":error"; - private const string PC_None = ":none"; + public static readonly StyledProperty IconProperty = AvaloniaProperty.Register( + nameof(Icon)); - private static readonly IReadOnlyDictionary _itemTypeMapping = new Dictionary + public object? Icon { - {TimelineItemType.Default, PC_Default}, - {TimelineItemType.Ongoing, PC_Ongoing}, - {TimelineItemType.Success, PC_Success}, - {TimelineItemType.Warning, PC_Warning}, - {TimelineItemType.Error, PC_Error}, - }; - - public static readonly StyledProperty IconForegroundProperty = - AvaloniaProperty.Register(nameof(IconForeground)); - - public IBrush IconForeground - { - get => GetValue(IconForegroundProperty); - set => SetValue(IconForegroundProperty, value); + get => GetValue(IconProperty); + set => SetValue(IconProperty, value); } - public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( - nameof(Time)); - public DateTime Time + public static readonly StyledProperty IconTemplateProperty = AvaloniaProperty.Register( + nameof(IconTemplate)); + + public IDataTemplate? IconTemplate { - get => GetValue(TimeProperty); - set => SetValue(TimeProperty, value); + get => GetValue(IconTemplateProperty); + set => SetValue(IconTemplateProperty, value); } - public static readonly StyledProperty TimeFormatProperty = AvaloniaProperty.Register( - nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern); + public static readonly StyledProperty TypeProperty = AvaloniaProperty.Register( + nameof(Type)); - public string? TimeFormat + public TimelineItemType Type { - get => GetValue(TimeFormatProperty); - set => SetValue(TimeFormatProperty, value); - } - - public static readonly StyledProperty DescriptionTemplateProperty = AvaloniaProperty.Register( - nameof(DescriptionTemplate)); - - public IDataTemplate DescriptionTemplate - { - get => GetValue(DescriptionTemplateProperty); - set => SetValue(DescriptionTemplateProperty, value); - } - - public static readonly StyledProperty ItemTypeProperty = AvaloniaProperty.Register( - nameof(ItemType)); - - public TimelineItemType ItemType - { - get => GetValue(ItemTypeProperty); - set => SetValue(ItemTypeProperty, value); - } - - internal void SetIndex(bool isFirst, bool isLast) - { - PseudoClasses.Set(PC_First, isFirst); - PseudoClasses.Set(PC_Last, isLast); - } - - static TimelineItem() - { - ItemTypeProperty.Changed.AddClassHandler((o, e) => { o.OnItemTypeChanged(e); }); - IconForegroundProperty.Changed.AddClassHandler((o, e) => { o.OnIconForegroundChanged(e); }); - } - - private void OnItemTypeChanged(AvaloniaPropertyChangedEventArgs args) - { - var oldValue = args.GetOldValue(); - var newValue = args.GetNewValue(); - PseudoClasses.Set(_itemTypeMapping[oldValue], false); - PseudoClasses.Set(_itemTypeMapping[newValue], true); - } - - private void OnIconForegroundChanged(AvaloniaPropertyChangedEventArgs args) - { - IBrush? newValue = args.GetOldValue(); - PseudoClasses.Set(PC_None, newValue is null); + get => GetValue(TypeProperty); + set => SetValue(TypeProperty, value); } + + } \ No newline at end of file From 3e95f191a091bc4b966700409c05922e4a52c463 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 27 Dec 2023 01:27:07 +0800 Subject: [PATCH 02/14] feat: add icon selector demo. --- .../Converters/TimelineIconConverter.cs | 32 +++++++++++++++++++ demo/Ursa.Demo/Pages/TimelineDemo.axaml | 14 ++++++++ .../TimelineIconTemplateSelector.cs | 30 +++++++++++++++++ .../ViewModels/TimelineDemoViewModel.cs | 4 +-- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 11 +++---- 5 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 demo/Ursa.Demo/Converters/TimelineIconConverter.cs create mode 100644 demo/Ursa.Demo/TemplateSelectors/TimelineIconTemplateSelector.cs diff --git a/demo/Ursa.Demo/Converters/TimelineIconConverter.cs b/demo/Ursa.Demo/Converters/TimelineIconConverter.cs new file mode 100644 index 0000000..04d46f4 --- /dev/null +++ b/demo/Ursa.Demo/Converters/TimelineIconConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using Avalonia; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Media; +using Ursa.Controls; + +namespace Ursa.Demo.Converters; + +public class TimelineIconConverter: IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is TimelineItemType t) + { + return t switch + { + TimelineItemType.Success => Brushes.Green, + TimelineItemType.Ongoing => Brushes.Blue, + TimelineItemType.Error => Brushes.Red, + _ => Brushes.Gray + }; + } + return AvaloniaProperty.UnsetValue; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index ad01c4f..1906f87 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -6,15 +6,29 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:u="https://irihi.tech/ursa" xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels" + xmlns:selectors="clr-namespace:Ursa.Demo.TemplateSelectors" d:DesignHeight="450" d:DesignWidth="800" x:DataType="viewModels:TimelineDemoViewModel" x:CompileBindings="True" mc:Ignorable="d"> + + + + + + + + + + + diff --git a/demo/Ursa.Demo/TemplateSelectors/TimelineIconTemplateSelector.cs b/demo/Ursa.Demo/TemplateSelectors/TimelineIconTemplateSelector.cs new file mode 100644 index 0000000..ceba90a --- /dev/null +++ b/demo/Ursa.Demo/TemplateSelectors/TimelineIconTemplateSelector.cs @@ -0,0 +1,30 @@ +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Controls.Templates; +using Avalonia.Media; +using Ursa.Controls; + +namespace Ursa.Demo.TemplateSelectors; + +public class TimelineIconTemplateSelector: ResourceDictionary, IDataTemplate +{ + + public Control? Build(object? param) + { + if (param is TimelineItemType t) + { + string s = t.ToString(); + if (ContainsKey(s)) + { + object? o = this[s]; + if (o is Control c) return c; + } + } + return null; + } + + public bool Match(object? data) + { + return data is TimelineItemType; + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs index ece4543..73e1335 100644 --- a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs @@ -22,7 +22,7 @@ public class TimelineDemoViewModel: ViewModelBase TimeFormat = "HH:mm:ss", Description = "Item 2", Header = "发布成功", - ItemType = TimelineItemType.Success, + ItemType = TimelineItemType.Ongoing, }, new() { @@ -30,7 +30,7 @@ public class TimelineDemoViewModel: ViewModelBase TimeFormat = "HH:mm:ss", Description = "Item 3", Header = "审核失败", - ItemType = TimelineItemType.Ongoing, + ItemType = TimelineItemType.Error, } }; } diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index 759be18..7b192ce 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -50,14 +50,11 @@ Classes="start" Fill="{DynamicResource TimelineLineBrush}" /> - + VerticalAlignment="Center" + ContentTemplate="{TemplateBinding IconTemplate}"/> Date: Wed, 27 Dec 2023 01:36:15 +0800 Subject: [PATCH 03/14] feat: start/end effect. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 6 +++--- src/Ursa/Controls/Timeline/Timeline.cs | 3 +++ src/Ursa/Controls/Timeline/TimelineItem.cs | 9 +++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index 1906f87..39e428a 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -32,9 +32,9 @@ > - - - + + + diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs index e6d25f3..1586733 100644 --- a/src/Ursa/Controls/Timeline/Timeline.cs +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -83,6 +83,9 @@ public class Timeline: ItemsControl base.PrepareContainerForItemOverride(container, item, index); if (container is TimelineItem t) { + bool start = index == 0; + bool end = index == ItemCount - 1; + t.SetEnd(start, end); if (IconMemberBinding != null) { t.Bind(TimelineItem.IconProperty, IconMemberBinding); diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 9fe8af4..20e57d6 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -8,6 +8,7 @@ using Avalonia.Media; namespace Ursa.Controls; +[PseudoClasses(":first", ":last")] public class TimelineItem: HeaderedContentControl { public static readonly StyledProperty IconProperty = AvaloniaProperty.Register( @@ -36,6 +37,10 @@ public class TimelineItem: HeaderedContentControl get => GetValue(TypeProperty); set => SetValue(TypeProperty, value); } - - + + internal void SetEnd(bool start, bool end) + { + PseudoClasses.Set(":first", start); + PseudoClasses.Set(":last", end); + } } \ No newline at end of file From 1d6d8feaecf835c88d7e3e315281906f07c96cd7 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 2 Jan 2024 22:32:25 +0800 Subject: [PATCH 04/14] wip: layout. --- .../Ursa.Demo.Desktop.csproj | 1 + demo/Ursa.Demo/Pages/TimelineDemo.axaml | 64 +++++++++++---- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 79 +++++++------------ src/Ursa/Controls/Timeline/Timeline.cs | 26 ++++++ .../Controls/Timeline/TimelineDisplayMode.cs | 9 +++ src/Ursa/Controls/Timeline/TimelineItem.cs | 47 ++++++++++- src/Ursa/Controls/Timeline/TimelinePanel.cs | 43 ++++++++++ 7 files changed, 197 insertions(+), 72 deletions(-) create mode 100644 src/Ursa/Controls/Timeline/TimelineDisplayMode.cs create mode 100644 src/Ursa/Controls/Timeline/TimelinePanel.cs diff --git a/demo/Ursa.Demo.Desktop/Ursa.Demo.Desktop.csproj b/demo/Ursa.Demo.Desktop/Ursa.Demo.Desktop.csproj index 3d3f388..64e44ce 100644 --- a/demo/Ursa.Demo.Desktop/Ursa.Demo.Desktop.csproj +++ b/demo/Ursa.Demo.Desktop/Ursa.Demo.Desktop.csproj @@ -6,6 +6,7 @@ net7.0 enable true + false diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index 39e428a..04d12b4 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -4,37 +4,67 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:selectors="clr-namespace:Ursa.Demo.TemplateSelectors" xmlns:u="https://irihi.tech/ursa" xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels" - xmlns:selectors="clr-namespace:Ursa.Demo.TemplateSelectors" d:DesignHeight="450" d:DesignWidth="800" - x:DataType="viewModels:TimelineDemoViewModel" x:CompileBindings="True" + x:DataType="viewModels:TimelineDemoViewModel" mc:Ignorable="d"> - - - - - + + + + + - - + - - - + + + diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index 7b192ce..dd7c25f 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -26,48 +26,35 @@ + WarningBrush="{DynamicResource WarningTimelineIconForeground}" /> - - + - - - - - - + Grid.Column="1" + Width="1" + Height="8" + HorizontalAlignment="Center" + VerticalAlignment="Top" + Classes="start" + Fill="{DynamicResource TimelineLineBrush}" /> + - + Foreground="Gray" /> - - - - - - diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs index 1586733..3577226 100644 --- a/src/Ursa/Controls/Timeline/Timeline.cs +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -6,12 +6,15 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Layout; using Avalonia.Metadata; namespace Ursa.Controls; public class Timeline: ItemsControl { + private static readonly FuncTemplate DefaultPanel = new((Func)(() => new TimelinePanel())); + public static readonly StyledProperty IconMemberBindingProperty = AvaloniaProperty.Register( nameof(IconMemberBinding)); @@ -66,6 +69,29 @@ public class Timeline: ItemsControl set => SetValue(DescriptionTemplateProperty, value); } + public static readonly StyledProperty ModeProperty = AvaloniaProperty.Register( + nameof(Mode)); + + public TimelineDisplayMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + static Timeline() + { + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + ModeProperty.Changed.AddClassHandler((t, e) => { t.OnDisplayModeChanged(e); }); + } + + private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e) + { + if (this.ItemsPanelRoot is TimelinePanel panel) + { + panel.Mode = e.NewValue.Value; + } + } + protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { recycleKey = null; diff --git a/src/Ursa/Controls/Timeline/TimelineDisplayMode.cs b/src/Ursa/Controls/Timeline/TimelineDisplayMode.cs new file mode 100644 index 0000000..1907672 --- /dev/null +++ b/src/Ursa/Controls/Timeline/TimelineDisplayMode.cs @@ -0,0 +1,9 @@ +namespace Ursa.Controls; + +public enum TimelineDisplayMode +{ + Left, + Center, + Right, + Alternate, +} \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 20e57d6..8f08983 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -8,9 +8,13 @@ using Avalonia.Media; namespace Ursa.Controls; -[PseudoClasses(":first", ":last")] +[PseudoClasses(PC_First, PC_Last, PC_EmptyIcon)] public class TimelineItem: HeaderedContentControl { + public const string PC_First = ":first"; + public const string PC_Last = ":last"; + public const string PC_EmptyIcon = ":empty-icon"; + public static readonly StyledProperty IconProperty = AvaloniaProperty.Register( nameof(Icon)); @@ -37,10 +41,47 @@ public class TimelineItem: HeaderedContentControl get => GetValue(TypeProperty); set => SetValue(TypeProperty, value); } + + public static readonly DirectProperty LeftWidthProperty = AvaloniaProperty.RegisterDirect( + nameof(LeftWidth), o => o.LeftWidth, (o, v) => o.LeftWidth = v); + private double _leftWidth; + public double LeftWidth + { + get => _leftWidth; + set => SetAndRaise(LeftWidthProperty, ref _leftWidth, value); + } + + public static readonly DirectProperty IconWidthProperty = AvaloniaProperty.RegisterDirect( + nameof(IconWidth), o => o.IconWidth, (o, v) => o.IconWidth = v); + private double _iconWidth; + public double IconWidth + { + get => _iconWidth; + set => SetAndRaise(IconWidthProperty, ref _iconWidth, value); + } + + public static readonly DirectProperty RightWidthProperty = AvaloniaProperty.RegisterDirect( + nameof(RightWidth), o => o.RightWidth, (o, v) => o.RightWidth = v); + private double _rightWidth; + public double RightWidth + { + get => _rightWidth; + set => SetAndRaise(RightWidthProperty, ref _rightWidth, value); + } + + static TimelineItem() + { + IconProperty.Changed.AddClassHandler((item, args) => { item.OnIconChanged(args); }); + } + + private void OnIconChanged(AvaloniaPropertyChangedEventArgs args) + { + PseudoClasses.Set(PC_EmptyIcon, args.NewValue.Value is null); + } internal void SetEnd(bool start, bool end) { - PseudoClasses.Set(":first", start); - PseudoClasses.Set(":last", end); + PseudoClasses.Set(PC_First, start); + PseudoClasses.Set(PC_Last, end); } } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelinePanel.cs b/src/Ursa/Controls/Timeline/TimelinePanel.cs new file mode 100644 index 0000000..ea6efba --- /dev/null +++ b/src/Ursa/Controls/Timeline/TimelinePanel.cs @@ -0,0 +1,43 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Layout; + +namespace Ursa.Controls; + +public class TimelinePanel: Panel +{ + public static readonly StyledProperty ModeProperty = + Timeline.ModeProperty.AddOwner(); + + public TimelineDisplayMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + static TimelinePanel() + { + AffectsMeasure(ModeProperty); + } + + protected override Size MeasureOverride(Size availableSize) + { + double left = 0; + double right = 0; + double icon = 0; + foreach (var child in Children) + { + if (child is TimelineItem t) + { + + } + } + return base.MeasureOverride(availableSize); + } + + protected override Size ArrangeOverride(Size finalSize) + { + return base.ArrangeOverride(finalSize); + + } +} \ No newline at end of file From 66bc512ae23febbac41be03b0f69f58056a87f8e Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 4 Jan 2024 20:33:42 +0800 Subject: [PATCH 05/14] WIP: layout with new panel. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 9 +- .../ViewModels/TimelineDemoViewModel.cs | 3 - src/Ursa.Themes.Semi/Controls/Timeline.axaml | 132 +++++++++++++----- src/Ursa/Controls/Timeline/Timeline.cs | 26 ++++ .../Controls/Timeline/TimelineDisplayMode.cs | 7 + src/Ursa/Controls/Timeline/TimelineItem.cs | 83 +++++++++++ src/Ursa/Controls/Timeline/TimelinePanel.cs | 37 ++++- 7 files changed, 250 insertions(+), 47 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index 04d12b4..70bccc0 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -47,23 +47,26 @@ - + ItemsSource="{Binding Items}" + TimeMemberBinding="{ReflectionBinding Time}" /> + diff --git a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs index 73e1335..54cb092 100644 --- a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs @@ -11,7 +11,6 @@ public class TimelineDemoViewModel: ViewModelBase new() { Time = DateTime.Now, - TimeFormat = "yyyy-MM-dd HH:mm:ss", Description = "Item 1", Header = "审核中", ItemType = TimelineItemType.Success, @@ -19,7 +18,6 @@ public class TimelineDemoViewModel: ViewModelBase new() { Time = DateTime.Now, - TimeFormat = "HH:mm:ss", Description = "Item 2", Header = "发布成功", ItemType = TimelineItemType.Ongoing, @@ -27,7 +25,6 @@ public class TimelineDemoViewModel: ViewModelBase new() { Time = DateTime.Now, - TimeFormat = "HH:mm:ss", Description = "Item 3", Header = "审核失败", ItemType = TimelineItemType.Error, diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index dd7c25f..87d5e48 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -18,7 +18,7 @@ - + @@ -32,51 +32,64 @@ WarningBrush="{DynamicResource WarningTimelineIconForeground}" /> + + - - + + - - + RowDefinitions="Auto, *"> + + + + ContentTemplate="{TemplateBinding HeaderTemplate}" /> + + + + + + + + @@ -86,12 +99,57 @@ - + + + + + + + + + + + + diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs index 3577226..f6809ca 100644 --- a/src/Ursa/Controls/Timeline/Timeline.cs +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -69,6 +69,27 @@ public class Timeline: ItemsControl set => SetValue(DescriptionTemplateProperty, value); } + public static readonly StyledProperty TimeMemberBindingProperty = AvaloniaProperty.Register( + nameof(TimeMemberBinding)); + + [AssignBinding] + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IBinding? TimeMemberBinding + { + get => GetValue(TimeMemberBindingProperty); + set => SetValue(TimeMemberBindingProperty, value); + } + + public static readonly StyledProperty TimeFormatProperty = AvaloniaProperty.Register( + nameof(TimeFormat), defaultValue:"yyyy-MM-dd HH:mm:ss"); + + public string? TimeFormat + { + get => GetValue(TimeFormatProperty); + set => SetValue(TimeFormatProperty, value); + } + + public static readonly StyledProperty ModeProperty = AvaloniaProperty.Register( nameof(Mode)); @@ -124,6 +145,11 @@ public class Timeline: ItemsControl { t.Bind(ContentControl.ContentProperty, DescriptionMemberBinding); } + if (TimeMemberBinding != null) + { + t.Bind(TimelineItem.TimeProperty, TimeMemberBinding); + } + t.SetCurrentValue(TimelineItem.TimeFormatProperty, TimeFormat); t.SetCurrentValue(TimelineItem.IconTemplateProperty, IconTemplate); t.SetCurrentValue(HeaderedContentControl.HeaderTemplateProperty, ItemTemplate); t.SetCurrentValue(ContentControl.ContentTemplateProperty, DescriptionTemplate); diff --git a/src/Ursa/Controls/Timeline/TimelineDisplayMode.cs b/src/Ursa/Controls/Timeline/TimelineDisplayMode.cs index 1907672..9ecfcd9 100644 --- a/src/Ursa/Controls/Timeline/TimelineDisplayMode.cs +++ b/src/Ursa/Controls/Timeline/TimelineDisplayMode.cs @@ -6,4 +6,11 @@ public enum TimelineDisplayMode Center, Right, Alternate, +} + +public enum TimelineItemDisplayMode +{ + Left, + Right, + Separate, } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 8f08983..4b63f70 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; @@ -9,11 +10,30 @@ using Avalonia.Media; namespace Ursa.Controls; [PseudoClasses(PC_First, PC_Last, PC_EmptyIcon)] +[TemplatePart(PART_Header, typeof(ContentPresenter))] +[TemplatePart(PART_Icon, typeof(ContentPresenter))] +[TemplatePart(PART_Content, typeof(ContentPresenter))] +[TemplatePart(PART_Time, typeof(TextBlock))] +[TemplatePart(PART_RootGrid, typeof(Grid))] public class TimelineItem: HeaderedContentControl { public const string PC_First = ":first"; public const string PC_Last = ":last"; public const string PC_EmptyIcon = ":empty-icon"; + public const string PC_AllLeft=":all-left"; + public const string PC_AllRight=":all-right"; + public const string PC_Separate = ":separate"; + public const string PART_Header = "PART_Header"; + public const string PART_Icon = "PART_Icon"; + public const string PART_Content = "PART_Content"; + public const string PART_Time = "PART_Time"; + public const string PART_RootGrid = "PART_RootGrid"; + + private ContentPresenter? _headerPresenter; + private ContentPresenter? _iconPresenter; + private ContentPresenter? _contentPresenter; + private TextBlock? _timePresenter; + private Grid? _rootGrid; public static readonly StyledProperty IconProperty = AvaloniaProperty.Register( nameof(Icon)); @@ -41,6 +61,15 @@ public class TimelineItem: HeaderedContentControl get => GetValue(TypeProperty); set => SetValue(TypeProperty, value); } + + public static readonly StyledProperty ModeProperty = AvaloniaProperty.Register( + nameof(Mode), defaultValue: TimelineItemDisplayMode.Right); + + public TimelineItemDisplayMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } public static readonly DirectProperty LeftWidthProperty = AvaloniaProperty.RegisterDirect( nameof(LeftWidth), o => o.LeftWidth, (o, v) => o.LeftWidth = v); @@ -69,9 +98,53 @@ public class TimelineItem: HeaderedContentControl set => SetAndRaise(RightWidthProperty, ref _rightWidth, value); } + public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( + nameof(Time)); + + public DateTime Time + { + get => GetValue(TimeProperty); + set => SetValue(TimeProperty, value); + } + + public static readonly StyledProperty TimeFormatProperty = AvaloniaProperty.Register( + nameof(TimeFormat)); + + public string? TimeFormat + { + get => GetValue(TimeFormatProperty); + set => SetValue(TimeFormatProperty, value); + } + static TimelineItem() { IconProperty.Changed.AddClassHandler((item, args) => { item.OnIconChanged(args); }); + ModeProperty.Changed.AddClassHandler((item, args) => { item.OnModeChanged(args); }); + AffectsMeasure(LeftWidthProperty, RightWidthProperty, IconWidthProperty); + } + + private void OnModeChanged(AvaloniaPropertyChangedEventArgs args) + { + SetMode(args.NewValue.Value); + } + + private void SetMode(TimelineItemDisplayMode mode) + { + PseudoClasses.Set(PC_AllLeft, mode == TimelineItemDisplayMode.Left); + PseudoClasses.Set(PC_AllRight, mode == TimelineItemDisplayMode.Right); + PseudoClasses.Set(PC_Separate, mode == TimelineItemDisplayMode.Separate); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + PseudoClasses.Set(PC_EmptyIcon, Icon is null); + _headerPresenter = e.NameScope.Find(PART_Header); + _iconPresenter = e.NameScope.Find(PART_Icon); + _contentPresenter = e.NameScope.Find(PART_Content); + _timePresenter = e.NameScope.Find(PART_Time); + _rootGrid = e.NameScope.Find(PART_RootGrid); + SetMode(Mode); } private void OnIconChanged(AvaloniaPropertyChangedEventArgs args) @@ -84,4 +157,14 @@ public class TimelineItem: HeaderedContentControl PseudoClasses.Set(PC_First, start); PseudoClasses.Set(PC_Last, end); } + + internal (double?, double?, double?, double?) GetWidth() + { + return (_headerPresenter?.Bounds.Width, _contentPresenter?.Bounds.Width, _iconPresenter?.Bounds.Width, _timePresenter?.Bounds.Width); + } + + internal void SetWidth(double? header, double? content, double? icon, double? time) + { + _rootGrid.ColumnDefinitions[0].Width = new GridLength(200); + } } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelinePanel.cs b/src/Ursa/Controls/Timeline/TimelinePanel.cs index ea6efba..37371f4 100644 --- a/src/Ursa/Controls/Timeline/TimelinePanel.cs +++ b/src/Ursa/Controls/Timeline/TimelinePanel.cs @@ -25,19 +25,48 @@ public class TimelinePanel: Panel double left = 0; double right = 0; double icon = 0; + double width = 0; + double height = 0; + foreach (var child in Children) + { + child.Measure(availableSize); + if (child is TimelineItem t) + { + var doubles = t.GetWidth(); + } + width = Math.Max(width, child.DesiredSize.Width); + height+=child.DesiredSize.Height; + } foreach (var child in Children) { if (child is TimelineItem t) { - + t.LeftWidth = left; + t.RightWidth = right; + t.IconWidth = icon; } } - return base.MeasureOverride(availableSize); + + return new Size(width, height); } protected override Size ArrangeOverride(Size finalSize) { - return base.ArrangeOverride(finalSize); - + Rect rect = new Rect(); + foreach (var child in Children) + { + rect = rect.WithWidth(Math.Max(rect.Width, child.DesiredSize.Width)); + rect = rect.WithHeight(rect.Height + child.DesiredSize.Height); + child.Arrange(rect); + rect = rect.WithY(rect.Y+child.DesiredSize.Height); + if (child is TimelineItem t) + { + var doubles = t.GetWidth(); + t.SetWidth(0, 0, 0, 0); + } + + } + //return base.ArrangeOverride(finalSize); + return rect.Size; } } \ No newline at end of file From 86b71a3c82f712929e8ead8304ca1522ba524049 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 4 Jan 2024 23:17:34 +0800 Subject: [PATCH 06/14] feat: new measure method. --- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 28 ++++++++++- src/Ursa/Controls/Timeline/Timeline.cs | 7 +++ src/Ursa/Controls/Timeline/TimelineItem.cs | 30 +++++++++-- src/Ursa/Controls/Timeline/TimelinePanel.cs | 52 ++++++++++++-------- 4 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index 87d5e48..3278740 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -18,7 +18,7 @@ - + @@ -68,19 +68,25 @@ Name="PART_Header" Margin="8 4" Content="{TemplateBinding Header}" + Foreground="{DynamicResource SemiGrey9}" + FontSize="14" ContentTemplate="{TemplateBinding HeaderTemplate}" /> @@ -113,42 +119,60 @@ diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs index f6809ca..bcb6cfb 100644 --- a/src/Ursa/Controls/Timeline/Timeline.cs +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -156,4 +156,11 @@ public class Timeline: ItemsControl } } + + protected override Size ArrangeOverride(Size finalSize) + { + var panel = this.ItemsPanelRoot as TimelinePanel; + panel.Mode = this.Mode; + return base.ArrangeOverride(finalSize); + } } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 4b63f70..e40841f 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -158,13 +158,35 @@ public class TimelineItem: HeaderedContentControl PseudoClasses.Set(PC_Last, end); } - internal (double?, double?, double?, double?) GetWidth() + internal (double left, double mid, double right) GetWidth() { - return (_headerPresenter?.Bounds.Width, _contentPresenter?.Bounds.Width, _iconPresenter?.Bounds.Width, _timePresenter?.Bounds.Width); + if (_headerPresenter is null) return new ValueTuple(0, 0, 0); + double header = _headerPresenter?.DesiredSize.Width ?? 0; + double icon = _iconPresenter?.DesiredSize.Width ?? 0; + double content = _contentPresenter?.DesiredSize.Width ?? 0; + double time = _timePresenter?.DesiredSize.Width ?? 0; + double max = Math.Max(header, content); + if (Mode == TimelineItemDisplayMode.Left) + { + max = Math.Max(max, time); + return (max, icon, 0); + } + if (Mode == TimelineItemDisplayMode.Right) + { + max = Math.Max(max, time); + return (0, icon, max); + } + if (Mode == TimelineItemDisplayMode.Separate) + { + return (time, icon, max); + } + return new ValueTuple(0, 0, 0); } - internal void SetWidth(double? header, double? content, double? icon, double? time) + internal void SetWidth(double? left, double? mid, double? right) { - _rootGrid.ColumnDefinitions[0].Width = new GridLength(200); + _rootGrid.ColumnDefinitions[0].Width = new GridLength(left??0); + _rootGrid.ColumnDefinitions[1].Width = new GridLength(mid??0); + _rootGrid.ColumnDefinitions[2].Width = new GridLength(right??0); } } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelinePanel.cs b/src/Ursa/Controls/Timeline/TimelinePanel.cs index 37371f4..8e0992e 100644 --- a/src/Ursa/Controls/Timeline/TimelinePanel.cs +++ b/src/Ursa/Controls/Timeline/TimelinePanel.cs @@ -25,7 +25,6 @@ public class TimelinePanel: Panel double left = 0; double right = 0; double icon = 0; - double width = 0; double height = 0; foreach (var child in Children) { @@ -33,40 +32,51 @@ public class TimelinePanel: Panel if (child is TimelineItem t) { var doubles = t.GetWidth(); + left = Math.Max(left, doubles.left); + icon = Math.Max(icon, doubles.mid); + right = Math.Max(right, doubles.right); } - width = Math.Max(width, child.DesiredSize.Width); height+=child.DesiredSize.Height; } - foreach (var child in Children) - { - if (child is TimelineItem t) - { - t.LeftWidth = left; - t.RightWidth = right; - t.IconWidth = icon; - } - } - - return new Size(width, height); + return new Size(left+icon+right, height); } protected override Size ArrangeOverride(Size finalSize) { - Rect rect = new Rect(); + + double left = 0, mid = 0, right = 0; + double height = 0; foreach (var child in Children) { - rect = rect.WithWidth(Math.Max(rect.Width, child.DesiredSize.Width)); - rect = rect.WithHeight(rect.Height + child.DesiredSize.Height); - child.Arrange(rect); - rect = rect.WithY(rect.Y+child.DesiredSize.Height); if (child is TimelineItem t) { var doubles = t.GetWidth(); - t.SetWidth(0, 0, 0, 0); + left = Math.Max(left, doubles.left); + mid = Math.Max(mid, doubles.mid); + right = Math.Max(right, doubles.right); + } + } + + Rect rect = new Rect(0, 0, left + mid + right, 0); + foreach (var child in Children) + { + if (child is TimelineItem t) + { + t.SetWidth(left, mid, right); + rect = rect.WithHeight(t.DesiredSize.Height); + t.InvalidateArrange(); + //rect = rect.WithHeight(t.DesiredSize.Height); + child.Arrange(rect); + rect = rect.WithY(rect.Y + t.DesiredSize.Height); + height+=t.DesiredSize.Height; } - } //return base.ArrangeOverride(finalSize); - return rect.Size; + return new Size(left + mid + right, height); + } + + public override void ApplyTemplate() + { + base.ApplyTemplate(); } } \ No newline at end of file From c6bc631e0d31b2fdde3ed4979bdbc6e9d7b6ea51 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 5 Jan 2024 00:08:15 +0800 Subject: [PATCH 07/14] feat: improve demo. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 61 ++++----- .../TimelineIconTemplateSelector.cs | 6 +- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 120 +++++++++--------- src/Ursa/Controls/Timeline/Timeline.cs | 69 ++++++++-- src/Ursa/Controls/Timeline/TimelinePanel.cs | 5 - 5 files changed, 161 insertions(+), 100 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index 70bccc0..e8f66b0 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -14,45 +14,48 @@ mc:Ignorable="d"> - - - - - - + + + + + + - + TimeMemberBinding="{ReflectionBinding Time}" > + + + + + + + - - + + - + + Foreground="{DynamicResource SemiGrey9}" /> + ContentTemplate="{TemplateBinding ContentTemplate}" + TextElement.FontSize="12" + TextElement.Foreground="Gray" /> + Foreground="Gray" + TextWrapping="Wrap"> - - + + @@ -117,62 +120,65 @@ diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs index bcb6cfb..467237f 100644 --- a/src/Ursa/Controls/Timeline/Timeline.cs +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -37,15 +37,15 @@ public class Timeline: ItemsControl set => SetValue(HeaderMemberBindingProperty, value); } - public static readonly StyledProperty DescriptionMemberBindingProperty = AvaloniaProperty.Register( - nameof(DescriptionMemberBinding)); + public static readonly StyledProperty ContentMemberBindingProperty = AvaloniaProperty.Register( + nameof(ContentMemberBinding)); [AssignBinding] [InheritDataTypeFromItems(nameof(ItemsSource))] - public IBinding? DescriptionMemberBinding + public IBinding? ContentMemberBinding { - get => GetValue(DescriptionMemberBindingProperty); - set => SetValue(DescriptionMemberBindingProperty, value); + get => GetValue(ContentMemberBindingProperty); + set => SetValue(ContentMemberBindingProperty, value); } @@ -110,6 +110,7 @@ public class Timeline: ItemsControl if (this.ItemsPanelRoot is TimelinePanel panel) { panel.Mode = e.NewValue.Value; + SetItemMode(); } } @@ -133,7 +134,7 @@ public class Timeline: ItemsControl bool start = index == 0; bool end = index == ItemCount - 1; t.SetEnd(start, end); - if (IconMemberBinding != null) + if (IconMemberBinding is not null) { t.Bind(TimelineItem.IconProperty, IconMemberBinding); } @@ -141,9 +142,9 @@ public class Timeline: ItemsControl { t.Bind(HeaderedContentControl.HeaderProperty, HeaderMemberBinding); } - if (DescriptionMemberBinding != null) + if (ContentMemberBinding != null) { - t.Bind(ContentControl.ContentProperty, DescriptionMemberBinding); + t.Bind(ContentControl.ContentProperty, ContentMemberBinding); } if (TimeMemberBinding != null) { @@ -161,6 +162,58 @@ public class Timeline: ItemsControl { var panel = this.ItemsPanelRoot as TimelinePanel; panel.Mode = this.Mode; + SetItemMode(); return base.ArrangeOverride(finalSize); } + + private void SetItemMode() + { + if (ItemsPanelRoot is TimelinePanel panel) + { + var items = panel.Children.OfType(); + if (Mode == TimelineDisplayMode.Left) + { + foreach (var item in items) + { + SetIfUnset(item, TimelineItem.ModeProperty, TimelineItemDisplayMode.Left); + } + } + else if (Mode == TimelineDisplayMode.Right) + { + foreach (var item in items) + { + SetIfUnset(item, TimelineItem.ModeProperty, TimelineItemDisplayMode.Right); + } + } + else if (Mode == TimelineDisplayMode.Center) + { + foreach (var item in items) + { + SetIfUnset(item, TimelineItem.ModeProperty, TimelineItemDisplayMode.Separate); + } + } + else if (Mode == TimelineDisplayMode.Alternate) + { + bool left = false; + foreach (var item in items) + { + if (left) + { + SetIfUnset(item, TimelineItem.ModeProperty, TimelineItemDisplayMode.Left); + } + else + { + SetIfUnset(item, TimelineItem.ModeProperty, TimelineItemDisplayMode.Right); + } + left = !left; + } + } + } + } + + private void SetIfUnset(AvaloniaObject target, StyledProperty property, T value) + { + if (!target.IsSet(property)) + target.SetCurrentValue(property, value); + } } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelinePanel.cs b/src/Ursa/Controls/Timeline/TimelinePanel.cs index 8e0992e..f1028b7 100644 --- a/src/Ursa/Controls/Timeline/TimelinePanel.cs +++ b/src/Ursa/Controls/Timeline/TimelinePanel.cs @@ -74,9 +74,4 @@ public class TimelinePanel: Panel //return base.ArrangeOverride(finalSize); return new Size(left + mid + right, height); } - - public override void ApplyTemplate() - { - base.ApplyTemplate(); - } } \ No newline at end of file From 907bbf16c98689970969a7eca312180bf75f9574 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 5 Jan 2024 00:14:35 +0800 Subject: [PATCH 08/14] feat: use static naming. --- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 8 ++++---- src/Ursa/Controls/Timeline/TimelineItem.cs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index 40d3b6a..ef7d859 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -48,7 +48,7 @@ Grid.Column="1" RowDefinitions="Auto, *"> (PART_Header); _iconPresenter = e.NameScope.Find(PART_Icon); _contentPresenter = e.NameScope.Find(PART_Content); _timePresenter = e.NameScope.Find(PART_Time); _rootGrid = e.NameScope.Find(PART_RootGrid); + PseudoClasses.Set(PC_EmptyIcon, Icon is null); SetMode(Mode); } @@ -185,6 +185,7 @@ public class TimelineItem: HeaderedContentControl internal void SetWidth(double? left, double? mid, double? right) { + if (_rootGrid is null) return; _rootGrid.ColumnDefinitions[0].Width = new GridLength(left??0); _rootGrid.ColumnDefinitions[1].Width = new GridLength(mid??0); _rootGrid.ColumnDefinitions[2].Width = new GridLength(right??0); From 32833d19ddf6679e6fe723178015d1dfd38ff18c Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 5 Jan 2024 00:27:48 +0800 Subject: [PATCH 09/14] feat: support type. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 18 +++++-- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 54 ++++++++++++-------- src/Ursa/Controls/Timeline/TimelineItem.cs | 8 +-- src/Ursa/Controls/Timeline/TimelinePanel.cs | 4 +- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index e8f66b0..4761739 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -23,7 +23,7 @@ - + + Type="Success" /> + Type="Warning" /> + + - + diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index ef7d859..bbb4ca8 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -47,17 +47,24 @@ Grid.RowSpan="3" Grid.Column="1" RowDefinitions="Auto, *"> - + + + + - - + + + + + - - - - + + + +