feat: add demo page, prepare collection changed behavior.
This commit is contained in:
25
demo/Ursa.Demo/Pages/TimelineDemo.axaml
Normal file
25
demo/Ursa.Demo/Pages/TimelineDemo.axaml
Normal 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>
|
||||||
15
demo/Ursa.Demo/Pages/TimelineDemo.axaml.cs
Normal file
15
demo/Ursa.Demo/Pages/TimelineDemo.axaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
110
demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs
Normal file
110
demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs
Normal 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; }
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
51
src/Ursa.Themes.Semi/Controls/Timeline.axaml
Normal file
51
src/Ursa.Themes.Semi/Controls/Timeline.axaml
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/Ursa/Controls/Timeline/TimelineFormatConverter.cs
Normal file
17
src/Ursa/Controls/Timeline/TimelineFormatConverter.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user