feat: add demo page, prepare collection changed behavior.

This commit is contained in:
rabbitism
2023-03-23 12:52:02 +08:00
parent 615ba9ce0a
commit 0f139264cc
11 changed files with 253 additions and 40 deletions

View File

@@ -0,0 +1,25 @@
<UserControl
x:Class="Ursa.Demo.Pages.TimelineDemo"
xmlns="https://github.com/avaloniaui"
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:u="https://irihi.tech/ursa"
xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="viewModels:TimelineDemoViewModel"
mc:Ignorable="d">
<u:Timeline ItemsSource="{Binding Items}">
<u:Timeline.ItemTemplate>
<DataTemplate x:DataType="viewModels:TimelineItemViewModel">
<u:TimelineItem
Content="{Binding Content}"
Time="{Binding Time}"
TimeFormat="{Binding TimeFormat}" />
</DataTemplate>
</u:Timeline.ItemTemplate>
</u:Timeline>
</UserControl>

View File

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

View File

@@ -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; }
}

View File

@@ -35,6 +35,9 @@
<TabItem Header="IPv4Box"> <TabItem Header="IPv4Box">
<pages:IPv4BoxDemo /> <pages:IPv4BoxDemo />
</TabItem> </TabItem>
<TabItem Header="Timeline">
<pages:TimelineDemo />
</TabItem>
</TabControl> </TabControl>
</Grid> </Grid>

View File

@@ -0,0 +1,51 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<Design.PreviewWith>
<StackPanel Width="100" Spacing="20">
<u:Timeline>
<u:TimelineItem Content="Hello" Time="2022-01-01" />
<u:TimelineItem Content="World" Time="2022-02-01" />
<u:TimelineItem Content="!" Time="2022-03-01" />
<u:TimelineItem />
</u:Timeline>
</StackPanel>
</Design.PreviewWith>
<!-- Add Resources Here -->
<u:TimelineFormatConverter x:Key="FormatConverter" />
<ControlTheme x:Key="{x:Type u:Timeline}" TargetType="u:Timeline">
<Setter Property="Template">
<ControlTemplate TargetType="u:Timeline">
<Grid Grid.IsSharedSizeScope="True">
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
</Grid>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:TimelineItem}" TargetType="u:TimelineItem">
<Setter Property="u:TimelineItem.Template">
<ControlTemplate TargetType="u:TimelineItem">
<StackPanel>
<ContentPresenter>
<ContentPresenter.Content>
<MultiBinding Converter="{StaticResource FormatConverter}">
<Binding Path="Time" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="TimeFormat" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</ContentPresenter.Content>
</ContentPresenter>
<ContentPresenter Name="content" Content="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter>
<Style Selector="^:first /template/ ContentPresenter#content">
<Setter Property="ContentPresenter.Foreground" Value="Red" />
</Style>
<Style Selector="^:last /template/ ContentPresenter#content">
<Setter Property="ContentPresenter.Foreground" Value="Green" />
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -5,5 +5,6 @@
<ResourceInclude Source="Banner.axaml" /> <ResourceInclude Source="Banner.axaml" />
<ResourceInclude Source="Divider.axaml" /> <ResourceInclude Source="Divider.axaml" />
<ResourceInclude Source="IPv4Box.axaml" /> <ResourceInclude Source="IPv4Box.axaml" />
<ResourceInclude Source="Timeline.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -1,6 +1,11 @@
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.ComTypes;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
namespace Ursa.Controls; namespace Ursa.Controls;
@@ -17,33 +22,33 @@ public class Timeline: ItemsControl
set => SetValue(ItemDescriptionTemplateProperty, value); 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); base.OnPropertyChanged(change);
if (container is TimelineItem c ) 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; t.SetPosition(i == 0, i == this.LogicalChildren.Count - 1);
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 else if (this.LogicalChildren[i] is ContentPresenter { Child: TimelineItem t2 })
{ {
c.Content = item; t2.SetPosition(i == 0, i == this.LogicalChildren.Count - 1);
if (ItemTemplate is { }) c[ContentControl.ContentTemplateProperty] = this.ItemTemplate;
} }
} }
} }

View File

@@ -0,0 +1,17 @@
using System.Globalization;
using Avalonia;
using Avalonia.Data.Converters;
namespace Ursa.Controls;
public class TimelineFormatConverter: IMultiValueConverter
{
public object? Convert(IList<object?> 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;
}
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
@@ -12,7 +13,7 @@ public class TimelineItem: ContentControl
{ {
public const string PC_First = ":first"; public const string PC_First = ":first";
public const string PC_Last = ":last"; public const string PC_Last = ":last";
public static readonly StyledProperty<IBrush> IconForegroundProperty = public static readonly StyledProperty<IBrush> IconForegroundProperty =
AvaloniaProperty.Register<TimelineItem, IBrush>(nameof(IconForeground)); AvaloniaProperty.Register<TimelineItem, IBrush>(nameof(IconForeground));
@@ -40,7 +41,7 @@ public class TimelineItem: ContentControl
} }
public static readonly StyledProperty<string?> TimeFormatProperty = AvaloniaProperty.Register<TimelineItem, string?>( public static readonly StyledProperty<string?> TimeFormatProperty = AvaloniaProperty.Register<TimelineItem, string?>(
nameof(TimeFormat)); nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.SortableDateTimePattern);
public string? TimeFormat public string? TimeFormat
{ {
@@ -56,4 +57,10 @@ public class TimelineItem: ContentControl
get => GetValue(DescriptionTemplateProperty); get => GetValue(DescriptionTemplateProperty);
set => SetValue(DescriptionTemplateProperty, value); set => SetValue(DescriptionTemplateProperty, value);
} }
internal void SetPosition(bool isFirst, bool isLast)
{
PseudoClasses.Set(PC_First, isFirst);
PseudoClasses.Set(PC_Last, isLast);
}
} }

View File

@@ -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; }
}

View File

@@ -1,12 +0,0 @@
namespace Ursa.Controls;
/// <summary>
/// Describe how TimelineItem components should be placed around the line.
/// </summary>
public enum TimelinePlacement
{
Left,
Right,
Center,
Alternate,
}