From 615ba9ce0aca54293106207302e1f53129ef85ea Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 23 Mar 2023 01:33:39 +0800 Subject: [PATCH 1/7] feat: initialize control. --- src/Ursa/Controls/Timeline/Timeline.cs | 50 ++++++++++++++++ src/Ursa/Controls/Timeline/TimelineItem.cs | 59 +++++++++++++++++++ .../Controls/Timeline/TimelineItemData.cs | 9 +++ .../Controls/Timeline/TimelineItemType.cs | 11 ++++ .../Controls/Timeline/TimelinePlacement.cs | 12 ++++ 5 files changed, 141 insertions(+) create mode 100644 src/Ursa/Controls/Timeline/Timeline.cs create mode 100644 src/Ursa/Controls/Timeline/TimelineItem.cs create mode 100644 src/Ursa/Controls/Timeline/TimelineItemData.cs create mode 100644 src/Ursa/Controls/Timeline/TimelineItemType.cs create mode 100644 src/Ursa/Controls/Timeline/TimelinePlacement.cs diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs new file mode 100644 index 0000000..34daef6 --- /dev/null +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -0,0 +1,50 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Generators; +using Avalonia.Controls.Templates; + +namespace Ursa.Controls; + +public class Timeline: ItemsControl +{ + + public static readonly StyledProperty ItemDescriptionTemplateProperty = AvaloniaProperty.Register( + nameof(ItemDescriptionTemplate)); + + public IDataTemplate? ItemDescriptionTemplate + { + get => GetValue(ItemDescriptionTemplateProperty); + set => SetValue(ItemDescriptionTemplateProperty, value); + } + + protected override bool IsItemItsOwnContainerOverride(Control item) + { + return item is TimelineItem; + } + + protected override Control CreateContainerForItemOverride() + { + return new TimelineItem(); + } + + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is TimelineItem c ) + { + if (item is ITimelineItemData data) + { + c[TimelineItem.TimeProperty] = data; + c[ContentControl.ContentProperty] = data.Content; + c[TimelineItem.DescriptionProperty] = data.Description; + if(ItemTemplate is {}) c[ContentControl.ContentTemplateProperty] = this.ItemTemplate; + if(ItemDescriptionTemplate is {}) c[TimelineItem.DescriptionTemplateProperty] = this.ItemDescriptionTemplate; + } + else + { + c.Content = item; + if (ItemTemplate is { }) c[ContentControl.ContentTemplateProperty] = this.ItemTemplate; + } + } + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs new file mode 100644 index 0000000..8628063 --- /dev/null +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -0,0 +1,59 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Media; + +namespace Ursa.Controls; + +[PseudoClasses(PC_First, PC_Last)] +public class TimelineItem: ContentControl +{ + public const string PC_First = ":first"; + public const string PC_Last = ":last"; + + public static readonly StyledProperty IconForegroundProperty = + AvaloniaProperty.Register(nameof(IconForeground)); + + public IBrush IconForeground + { + get => GetValue(IconForegroundProperty); + set => SetValue(IconForegroundProperty, value); + } + + public static readonly StyledProperty DescriptionProperty = + AvaloniaProperty.Register(nameof(Description)); + + public object? Description + { + get => GetValue(DescriptionProperty); + set => SetValue(DescriptionProperty, 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); + } + + public static readonly StyledProperty DescriptionTemplateProperty = AvaloniaProperty.Register( + nameof(DescriptionTemplate)); + + public IDataTemplate DescriptionTemplate + { + get => GetValue(DescriptionTemplateProperty); + set => SetValue(DescriptionTemplateProperty, value); + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItemData.cs b/src/Ursa/Controls/Timeline/TimelineItemData.cs new file mode 100644 index 0000000..1d4b7c0 --- /dev/null +++ b/src/Ursa/Controls/Timeline/TimelineItemData.cs @@ -0,0 +1,9 @@ +namespace Ursa.Controls; + +public interface ITimelineItemData +{ + public DateTime Time { get; set; } + public object Content { get; set; } + public object Description { get; set; } + public TimelineItemType ItemType { get; set; } +} \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItemType.cs b/src/Ursa/Controls/Timeline/TimelineItemType.cs new file mode 100644 index 0000000..2fa2f60 --- /dev/null +++ b/src/Ursa/Controls/Timeline/TimelineItemType.cs @@ -0,0 +1,11 @@ +namespace Ursa.Controls; + +public enum TimelineItemType +{ + None, + Default, + Ongoing, + Success, + Warning, + Error, +} \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelinePlacement.cs b/src/Ursa/Controls/Timeline/TimelinePlacement.cs new file mode 100644 index 0000000..adf11bb --- /dev/null +++ b/src/Ursa/Controls/Timeline/TimelinePlacement.cs @@ -0,0 +1,12 @@ +namespace Ursa.Controls; + +/// +/// Describe how TimelineItem components should be placed around the line. +/// +public enum TimelinePlacement +{ + Left, + Right, + Center, + Alternate, +} \ No newline at end of file From 0f139264ccd105bcf8e8f4b0f385cc6e560b8146 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 23 Mar 2023 12:52:02 +0800 Subject: [PATCH 2/7] feat: add demo page, prepare collection changed behavior. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 25 ++++ demo/Ursa.Demo/Pages/TimelineDemo.axaml.cs | 15 +++ .../ViewModels/TimelineDemoViewModel.cs | 110 ++++++++++++++++++ demo/Ursa.Demo/Views/MainWindow.axaml | 3 + src/Ursa.Themes.Semi/Controls/Timeline.axaml | 51 ++++++++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa/Controls/Timeline/Timeline.cs | 39 ++++--- .../Timeline/TimelineFormatConverter.cs | 17 +++ src/Ursa/Controls/Timeline/TimelineItem.cs | 11 +- .../Controls/Timeline/TimelineItemData.cs | 9 -- .../Controls/Timeline/TimelinePlacement.cs | 12 -- 11 files changed, 253 insertions(+), 40 deletions(-) create mode 100644 demo/Ursa.Demo/Pages/TimelineDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/TimelineDemo.axaml.cs create mode 100644 demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs create mode 100644 src/Ursa.Themes.Semi/Controls/Timeline.axaml create mode 100644 src/Ursa/Controls/Timeline/TimelineFormatConverter.cs delete mode 100644 src/Ursa/Controls/Timeline/TimelineItemData.cs delete mode 100644 src/Ursa/Controls/Timeline/TimelinePlacement.cs diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml new file mode 100644 index 0000000..674a2c4 --- /dev/null +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml.cs b/demo/Ursa.Demo/Pages/TimelineDemo.axaml.cs new file mode 100644 index 0000000..fe07b76 --- /dev/null +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml.cs @@ -0,0 +1,15 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Ursa.Demo.ViewModels; + +namespace Ursa.Demo.Pages; + +public partial class TimelineDemo : UserControl +{ + public TimelineDemo() + { + InitializeComponent(); + this.DataContext = new TimelineDemoViewModel(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs new file mode 100644 index 0000000..29f72f7 --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs @@ -0,0 +1,110 @@ +using System; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Ursa.Demo.ViewModels; + +public class TimelineDemoViewModel: ObservableObject +{ + public TimelineItemViewModel[] Items { get; } = + { + new() + { + Time = DateTime.Now, + TimeFormat = "yyyy-MM-dd HH:mm:ss", + Description = "Item 1", + Content = "First" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 2", + Content = "Content 2" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 3", + Content = "Content 3" + }, + 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" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 6", + Content = "Content 6" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 7", + Content = "Content 7" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 8", + Content = "Content 8" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 9", + Content = "Content 9" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 10", + Content = "Content 10" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 11", + Content = "Content 11" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 12", + Content = "Content 12" + }, + new() + { + Time = DateTime.Now, + TimeFormat = "HH:mm:ss", + Description = "Item 13", + Content = "Last" + } + }; +} + +public class TimelineItemViewModel: ObservableObject +{ + public DateTime Time { get; set; } + public string? TimeFormat { get; set; } + public string? Description { get; set; } + public string? Content { get; set; } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Views/MainWindow.axaml b/demo/Ursa.Demo/Views/MainWindow.axaml index de1f2dd..9ba93b8 100644 --- a/demo/Ursa.Demo/Views/MainWindow.axaml +++ b/demo/Ursa.Demo/Views/MainWindow.axaml @@ -35,6 +35,9 @@ + + + diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml new file mode 100644 index 0000000..002d236 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 4d15016..7c08f47 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -5,5 +5,6 @@ + diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs index 34daef6..23e52dc 100644 --- a/src/Ursa/Controls/Timeline/Timeline.cs +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -1,6 +1,11 @@ +using System.Collections.Specialized; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.ComTypes; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Generators; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; namespace Ursa.Controls; @@ -17,33 +22,33 @@ public class Timeline: ItemsControl set => SetValue(ItemDescriptionTemplateProperty, value); } - protected override bool IsItemItsOwnContainerOverride(Control item) + public Timeline() { - return item is TimelineItem; + ItemsView.CollectionChanged+=ItemsViewOnCollectionChanged; } - protected override Control CreateContainerForItemOverride() + private void ItemsViewOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - return new TimelineItem(); + RefreshTimelineItems(); } - - protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - base.PrepareContainerForItemOverride(container, item, index); - if (container is TimelineItem c ) + base.OnPropertyChanged(change); + RefreshTimelineItems(); + } + + private void RefreshTimelineItems() + { + for (int i = 0; i < this.LogicalChildren.Count; i++) { - if (item is ITimelineItemData data) + if (this.LogicalChildren[i] is TimelineItem t) { - c[TimelineItem.TimeProperty] = data; - c[ContentControl.ContentProperty] = data.Content; - c[TimelineItem.DescriptionProperty] = data.Description; - if(ItemTemplate is {}) c[ContentControl.ContentTemplateProperty] = this.ItemTemplate; - if(ItemDescriptionTemplate is {}) c[TimelineItem.DescriptionTemplateProperty] = this.ItemDescriptionTemplate; + t.SetPosition(i == 0, i == this.LogicalChildren.Count - 1); } - else + else if (this.LogicalChildren[i] is ContentPresenter { Child: TimelineItem t2 }) { - c.Content = item; - if (ItemTemplate is { }) c[ContentControl.ContentTemplateProperty] = this.ItemTemplate; + t2.SetPosition(i == 0, i == this.LogicalChildren.Count - 1); } } } diff --git a/src/Ursa/Controls/Timeline/TimelineFormatConverter.cs b/src/Ursa/Controls/Timeline/TimelineFormatConverter.cs new file mode 100644 index 0000000..48cdc8f --- /dev/null +++ b/src/Ursa/Controls/Timeline/TimelineFormatConverter.cs @@ -0,0 +1,17 @@ +using System.Globalization; +using Avalonia; +using Avalonia.Data.Converters; + +namespace Ursa.Controls; + +public class TimelineFormatConverter: IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values.Count> 1 && values[0] is DateTime date && values[1] is string s) + { + return date.ToString(s, culture); + } + return AvaloniaProperty.UnsetValue; + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 8628063..0139823 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; @@ -12,7 +13,7 @@ public class TimelineItem: ContentControl { public const string PC_First = ":first"; public const string PC_Last = ":last"; - + public static readonly StyledProperty IconForegroundProperty = AvaloniaProperty.Register(nameof(IconForeground)); @@ -40,7 +41,7 @@ public class TimelineItem: ContentControl } public static readonly StyledProperty TimeFormatProperty = AvaloniaProperty.Register( - nameof(TimeFormat)); + nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.SortableDateTimePattern); public string? TimeFormat { @@ -56,4 +57,10 @@ public class TimelineItem: ContentControl get => GetValue(DescriptionTemplateProperty); set => SetValue(DescriptionTemplateProperty, value); } + + internal void SetPosition(bool isFirst, bool isLast) + { + PseudoClasses.Set(PC_First, isFirst); + PseudoClasses.Set(PC_Last, isLast); + } } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItemData.cs b/src/Ursa/Controls/Timeline/TimelineItemData.cs deleted file mode 100644 index 1d4b7c0..0000000 --- a/src/Ursa/Controls/Timeline/TimelineItemData.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ursa.Controls; - -public interface ITimelineItemData -{ - public DateTime Time { get; set; } - public object Content { get; set; } - public object Description { get; set; } - public TimelineItemType ItemType { get; set; } -} \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelinePlacement.cs b/src/Ursa/Controls/Timeline/TimelinePlacement.cs deleted file mode 100644 index adf11bb..0000000 --- a/src/Ursa/Controls/Timeline/TimelinePlacement.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ursa.Controls; - -/// -/// Describe how TimelineItem components should be placed around the line. -/// -public enum TimelinePlacement -{ - Left, - Right, - Center, - Alternate, -} \ No newline at end of file From 36e3e88276abf3971064c164b611d00955c18a61 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 23 Mar 2023 14:53:43 +0800 Subject: [PATCH 3/7] feat: WIP: add demo and background for layout testing. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 88 ++++++++++++++++--- .../ViewModels/TimelineDemoViewModel.cs | 12 +-- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 28 ++++-- 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index 674a2c4..0093166 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -8,18 +8,82 @@ xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels" d:DesignHeight="450" d:DesignWidth="800" - x:CompileBindings="True" + x:CompileBindings="False" x:DataType="viewModels:TimelineDemoViewModel" mc:Ignorable="d"> - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs index 29f72f7..9c51cc4 100644 --- a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs @@ -54,42 +54,42 @@ public class TimelineDemoViewModel: ObservableObject Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 7", - Content = "Content 7" + Content = "Content 71231" }, new() { Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 8", - Content = "Content 8" + Content = "Content 8123123" }, new() { Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 9", - Content = "Content 9" + Content = "Content 9123123" }, new() { Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 10", - Content = "Content 10" + Content = "Content 1231231231231231231230" }, new() { Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 11", - Content = "Content 11" + Content = "Content 11231231" }, new() { Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 12", - Content = "Content 12" + Content = "Content 12123123123123" }, new() { diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index 002d236..8d01b88 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -27,8 +27,16 @@ - - + + + + + + + @@ -36,9 +44,19 @@ - - - + + + - diff --git a/src/Ursa/Controls/Timeline/Timeline.cs b/src/Ursa/Controls/Timeline/Timeline.cs index 23e52dc..3304ab0 100644 --- a/src/Ursa/Controls/Timeline/Timeline.cs +++ b/src/Ursa/Controls/Timeline/Timeline.cs @@ -44,12 +44,13 @@ public class Timeline: ItemsControl { if (this.LogicalChildren[i] is TimelineItem t) { - t.SetPosition(i == 0, i == this.LogicalChildren.Count - 1); + t.SetIndex(i == 0, i == this.LogicalChildren.Count - 1); } else if (this.LogicalChildren[i] is ContentPresenter { Child: TimelineItem t2 }) { - t2.SetPosition(i == 0, i == this.LogicalChildren.Count - 1); + t2.SetIndex(i == 0, i == this.LogicalChildren.Count - 1); } } } + } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 0139823..2760bb7 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -58,9 +58,58 @@ public class TimelineItem: ContentControl set => SetValue(DescriptionTemplateProperty, value); } - internal void SetPosition(bool isFirst, bool isLast) + internal void SetIndex(bool isFirst, bool isLast) { PseudoClasses.Set(PC_First, isFirst); PseudoClasses.Set(PC_Last, isLast); } +} + +public class TimelineItemLayoutProperties: AvaloniaObject +{ + private double _dimensionDelta = 0.01; + + public static readonly StyledProperty TimeSlotWidthProperty = AvaloniaProperty.Register( + nameof(TimeSlotWidth)); + + public double TimeSlotWidth + { + get => GetValue(TimeSlotWidthProperty); + set + { + if (Math.Abs(GetValue(TimeSlotWidthProperty) - value) < _dimensionDelta) return; + SetValue(TimeSlotWidthProperty, value); + } + } + + public static readonly StyledProperty TimeSlotHeightProperty = AvaloniaProperty.Register( + nameof(TimeSlotHeight)); + + public double TimeSlotHeight + { + get => GetValue(TimeSlotHeightProperty); + set + { + if (Math.Abs(GetValue(TimeSlotHeightProperty) - value) < _dimensionDelta) return; + SetValue(TimeSlotHeightProperty, value); + } + } + + public static readonly StyledProperty ContentSlotWidthProperty = AvaloniaProperty.Register( + nameof(ContentSlotWidth)); + + public double ContentSlotWidth + { + get => GetValue(ContentSlotWidthProperty); + set => SetValue(ContentSlotWidthProperty, value); + } + + public static readonly StyledProperty ContentSlotHeightProperty = AvaloniaProperty.Register( + nameof(ContentSlotHeight)); + + public double ContentSlotHeight + { + get => GetValue(ContentSlotHeightProperty); + set => SetValue(ContentSlotHeightProperty, value); + } } \ No newline at end of file From 2a38bb09a2a6603d9c3a86696550a80c97eb6b5e Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 23 Mar 2023 23:25:29 +0800 Subject: [PATCH 5/7] misc: update timelineitem style --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 15 +---- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 12 ++-- src/Ursa/Controls/Timeline/TimelineItem.cs | 60 +------------------- 3 files changed, 10 insertions(+), 77 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index d77dbca..0671c98 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -16,18 +16,9 @@ - - - + + + diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index b36677a..801865d 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -33,7 +33,7 @@ Width="1" VerticalAlignment="Stretch" Classes="end" - Fill="Gray" /> + Fill="LightGray" /> + Fill="LightGray" /> + Fill="LightGray" /> + Fill="LightGray" /> + Foreground="Gray"> diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 2760bb7..915644c 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -23,15 +23,6 @@ public class TimelineItem: ContentControl set => SetValue(IconForegroundProperty, value); } - public static readonly StyledProperty DescriptionProperty = - AvaloniaProperty.Register(nameof(Description)); - - public object? Description - { - get => GetValue(DescriptionProperty); - set => SetValue(DescriptionProperty, value); - } - public static readonly StyledProperty TimeProperty = AvaloniaProperty.Register( nameof(Time)); public DateTime Time @@ -41,7 +32,7 @@ public class TimelineItem: ContentControl } public static readonly StyledProperty TimeFormatProperty = AvaloniaProperty.Register( - nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.SortableDateTimePattern); + nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern); public string? TimeFormat { @@ -63,53 +54,4 @@ public class TimelineItem: ContentControl PseudoClasses.Set(PC_First, isFirst); PseudoClasses.Set(PC_Last, isLast); } -} - -public class TimelineItemLayoutProperties: AvaloniaObject -{ - private double _dimensionDelta = 0.01; - - public static readonly StyledProperty TimeSlotWidthProperty = AvaloniaProperty.Register( - nameof(TimeSlotWidth)); - - public double TimeSlotWidth - { - get => GetValue(TimeSlotWidthProperty); - set - { - if (Math.Abs(GetValue(TimeSlotWidthProperty) - value) < _dimensionDelta) return; - SetValue(TimeSlotWidthProperty, value); - } - } - - public static readonly StyledProperty TimeSlotHeightProperty = AvaloniaProperty.Register( - nameof(TimeSlotHeight)); - - public double TimeSlotHeight - { - get => GetValue(TimeSlotHeightProperty); - set - { - if (Math.Abs(GetValue(TimeSlotHeightProperty) - value) < _dimensionDelta) return; - SetValue(TimeSlotHeightProperty, value); - } - } - - public static readonly StyledProperty ContentSlotWidthProperty = AvaloniaProperty.Register( - nameof(ContentSlotWidth)); - - public double ContentSlotWidth - { - get => GetValue(ContentSlotWidthProperty); - set => SetValue(ContentSlotWidthProperty, value); - } - - public static readonly StyledProperty ContentSlotHeightProperty = AvaloniaProperty.Register( - nameof(ContentSlotHeight)); - - public double ContentSlotHeight - { - get => GetValue(ContentSlotHeightProperty); - set => SetValue(ContentSlotHeightProperty, value); - } } \ No newline at end of file From 6e5b3012dc0a4d7d3f87354a788eef91ded5bab4 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 24 Apr 2023 22:41:01 +0800 Subject: [PATCH 6/7] feat: add item styles, add color converter. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 20 ++++- src/Ursa.Themes.Semi/Controls/Timeline.axaml | 52 ++++++++---- ...melineItemTypeToIconForegroundConverter.cs | 82 +++++++++++++++++++ .../Themes/Light/Timeline.axaml | 8 ++ .../Themes/Light/_index.axaml | 1 + src/Ursa/Controls/Timeline/TimelineItem.cs | 43 +++++++++- .../Controls/Timeline/TimelineItemType.cs | 1 - 7 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 src/Ursa.Themes.Semi/Converters/TimelineItemTypeToIconForegroundConverter.cs create mode 100644 src/Ursa.Themes.Semi/Themes/Light/Timeline.axaml diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index 0671c98..cafed3a 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -16,9 +16,23 @@ - - - + + + + diff --git a/src/Ursa.Themes.Semi/Controls/Timeline.axaml b/src/Ursa.Themes.Semi/Controls/Timeline.axaml index 801865d..63e311c 100644 --- a/src/Ursa.Themes.Semi/Controls/Timeline.axaml +++ b/src/Ursa.Themes.Semi/Controls/Timeline.axaml @@ -1,6 +1,7 @@ @@ -22,48 +23,64 @@ + + - - - + + + + + + + + + + + DefaultBrushProperty = AvaloniaProperty.Register( + nameof(DefaultBrush)); + + public IBrush DefaultBrush + { + get => GetValue(DefaultBrushProperty); + set => SetValue(DefaultBrushProperty, value); + } + + public static readonly StyledProperty OngoingBrushProperty = AvaloniaProperty.Register( + nameof(OngoingBrush)); + + public IBrush OngoingBrush + { + get => GetValue(OngoingBrushProperty); + set => SetValue(OngoingBrushProperty, value); + } + + public static readonly StyledProperty SuccessBrushProperty = AvaloniaProperty.Register( + nameof(SuccessBrush)); + + public IBrush SuccessBrush + { + get => GetValue(SuccessBrushProperty); + set => SetValue(SuccessBrushProperty, value); + } + + public static readonly StyledProperty WarningBrushProperty = AvaloniaProperty.Register( + nameof(WarningBrush)); + + public IBrush WarningBrush + { + get => GetValue(WarningBrushProperty); + set => SetValue(WarningBrushProperty, value); + } + + public static readonly StyledProperty ErrorBrushProperty = AvaloniaProperty.Register( + nameof(ErrorBrush)); + + public IBrush ErrorBrush + { + get => GetValue(ErrorBrushProperty); + set => SetValue(ErrorBrushProperty, value); + } + + + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values[0] is TimelineItemType type) + { + switch (type) + { + case TimelineItemType.Error: + return ErrorBrush; + case TimelineItemType.Warning: + return WarningBrush; + case TimelineItemType.Success: + return SuccessBrush; + case TimelineItemType.Ongoing: + return OngoingBrush; + case TimelineItemType.Default: + if (values[1] is IBrush brush) + { + return brush; + } + return DefaultBrush; + } + } + return AvaloniaProperty.UnsetValue; + } +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Themes/Light/Timeline.axaml b/src/Ursa.Themes.Semi/Themes/Light/Timeline.axaml new file mode 100644 index 0000000..e879046 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Light/Timeline.axaml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml index 7a530cc..004d6a1 100644 --- a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml @@ -5,5 +5,6 @@ + diff --git a/src/Ursa/Controls/Timeline/TimelineItem.cs b/src/Ursa/Controls/Timeline/TimelineItem.cs index 915644c..9ba9f9c 100644 --- a/src/Ursa/Controls/Timeline/TimelineItem.cs +++ b/src/Ursa/Controls/Timeline/TimelineItem.cs @@ -8,11 +8,25 @@ using Avalonia.Media; namespace Ursa.Controls; -[PseudoClasses(PC_First, PC_Last)] +[PseudoClasses(PC_First, PC_Last, PC_Default, PC_Ongoing, PC_Success, PC_Warning, PC_Error)] public class TimelineItem: ContentControl { - public const string PC_First = ":first"; - public const string PC_Last = ":last"; + 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 static readonly IReadOnlyDictionary _itemTypeMapping = new Dictionary + { + {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)); @@ -49,9 +63,32 @@ public class TimelineItem: ContentControl 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); }); + } + + private void OnItemTypeChanged(AvaloniaPropertyChangedEventArgs args) + { + var oldValue = args.GetOldValue(); + var newValue = args.GetNewValue(); + PseudoClasses.Set(_itemTypeMapping[oldValue], false); + PseudoClasses.Set(_itemTypeMapping[newValue], true); + } + } \ No newline at end of file diff --git a/src/Ursa/Controls/Timeline/TimelineItemType.cs b/src/Ursa/Controls/Timeline/TimelineItemType.cs index 2fa2f60..6b90703 100644 --- a/src/Ursa/Controls/Timeline/TimelineItemType.cs +++ b/src/Ursa/Controls/Timeline/TimelineItemType.cs @@ -2,7 +2,6 @@ namespace Ursa.Controls; public enum TimelineItemType { - None, Default, Ongoing, Success, From a19bbe118fa45fcef4e85ddecf0f53b9b403f347 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 24 Apr 2023 22:54:49 +0800 Subject: [PATCH 7/7] feat: update demo. --- demo/Ursa.Demo/Pages/TimelineDemo.axaml | 1 + demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TimelineDemo.axaml b/demo/Ursa.Demo/Pages/TimelineDemo.axaml index cafed3a..b217fad 100644 --- a/demo/Ursa.Demo/Pages/TimelineDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimelineDemo.axaml @@ -39,6 +39,7 @@ diff --git a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs index 9c51cc4..2420ee3 100644 --- a/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs @@ -1,5 +1,6 @@ using System; using CommunityToolkit.Mvvm.ComponentModel; +using Ursa.Controls; namespace Ursa.Demo.ViewModels; @@ -12,21 +13,24 @@ public class TimelineDemoViewModel: ObservableObject Time = DateTime.Now, TimeFormat = "yyyy-MM-dd HH:mm:ss", Description = "Item 1", - Content = "First" + Content = "First", + ItemType = TimelineItemType.Success, }, new() { Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 2", - Content = "Content 2" + Content = "Content 2", + ItemType = TimelineItemType.Success, }, new() { Time = DateTime.Now, TimeFormat = "HH:mm:ss", Description = "Item 3", - Content = "Content 3" + Content = "Content 3", + ItemType = TimelineItemType.Ongoing, }, new() { @@ -107,4 +111,5 @@ public class TimelineItemViewModel: ObservableObject public string? TimeFormat { get; set; } public string? Description { get; set; } public string? Content { get; set; } + public TimelineItemType ItemType { get; set; } } \ No newline at end of file