58
demo/Ursa.Demo/Pages/TimelineDemo.axaml
Normal file
58
demo/Ursa.Demo/Pages/TimelineDemo.axaml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<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="False"
|
||||||
|
x:DataType="viewModels:TimelineDemoViewModel"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<u:TimelineFormatConverter x:Key="FormatConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<StackPanel>
|
||||||
|
<u:Timeline>
|
||||||
|
<u:TimelineItem
|
||||||
|
Content="Start"
|
||||||
|
ItemType="Warning"
|
||||||
|
Time="2022-01-01" />
|
||||||
|
<u:TimelineItem
|
||||||
|
Content="In between"
|
||||||
|
ItemType="Ongoing"
|
||||||
|
Time="2022-01-02" />
|
||||||
|
<u:TimelineItem
|
||||||
|
Content="Finished"
|
||||||
|
ItemType="Error"
|
||||||
|
Time="2022-01-03" />
|
||||||
|
<u:TimelineItem
|
||||||
|
Content="Finished"
|
||||||
|
IconForeground="Yellow"
|
||||||
|
ItemType="Default"
|
||||||
|
Time="2022-01-03" />
|
||||||
|
</u:Timeline>
|
||||||
|
<u:Timeline HorizontalAlignment="Left" ItemsSource="{Binding Items}">
|
||||||
|
<u:Timeline.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="viewModels:TimelineItemViewModel">
|
||||||
|
<u:TimelineItem
|
||||||
|
Content="{Binding Content}"
|
||||||
|
ItemType="{Binding ItemType}"
|
||||||
|
Time="{Binding Time}"
|
||||||
|
TimeFormat="{Binding TimeFormat}">
|
||||||
|
<u:TimelineItem.ContentTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock
|
||||||
|
MaxWidth="100"
|
||||||
|
Text="{Binding}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</DataTemplate>
|
||||||
|
</u:TimelineItem.ContentTemplate>
|
||||||
|
</u:TimelineItem>
|
||||||
|
</DataTemplate>
|
||||||
|
</u:Timeline.ItemTemplate>
|
||||||
|
</u:Timeline>
|
||||||
|
</StackPanel>
|
||||||
|
</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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
115
demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs
Normal file
115
demo/Ursa.Demo/ViewModels/TimelineDemoViewModel.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
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",
|
||||||
|
ItemType = TimelineItemType.Success,
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Time = DateTime.Now,
|
||||||
|
TimeFormat = "HH:mm:ss",
|
||||||
|
Description = "Item 2",
|
||||||
|
Content = "Content 2",
|
||||||
|
ItemType = TimelineItemType.Success,
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Time = DateTime.Now,
|
||||||
|
TimeFormat = "HH:mm:ss",
|
||||||
|
Description = "Item 3",
|
||||||
|
Content = "Content 3",
|
||||||
|
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"
|
||||||
|
},
|
||||||
|
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 71231"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Time = DateTime.Now,
|
||||||
|
TimeFormat = "HH:mm:ss",
|
||||||
|
Description = "Item 8",
|
||||||
|
Content = "Content 8123123"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Time = DateTime.Now,
|
||||||
|
TimeFormat = "HH:mm:ss",
|
||||||
|
Description = "Item 9",
|
||||||
|
Content = "Content 9123123"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Time = DateTime.Now,
|
||||||
|
TimeFormat = "HH:mm:ss",
|
||||||
|
Description = "Item 10",
|
||||||
|
Content = "Content 1231231231231231231230"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Time = DateTime.Now,
|
||||||
|
TimeFormat = "HH:mm:ss",
|
||||||
|
Description = "Item 11",
|
||||||
|
Content = "Content 11231231"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Time = DateTime.Now,
|
||||||
|
TimeFormat = "HH:mm:ss",
|
||||||
|
Description = "Item 12",
|
||||||
|
Content = "Content 12123123123123"
|
||||||
|
},
|
||||||
|
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; }
|
||||||
|
public TimelineItemType ItemType { 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>
|
||||||
|
|
||||||
|
|||||||
116
src/Ursa.Themes.Semi/Controls/Timeline.axaml
Normal file
116
src/Ursa.Themes.Semi/Controls/Timeline.axaml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:converters="clr-namespace:Ursa.Themes.Semi.Converters"
|
||||||
|
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">
|
||||||
|
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</ControlTheme>
|
||||||
|
|
||||||
|
<converters:TimelineItemTypeToIconForegroundConverter
|
||||||
|
x:Key="ForegroundConverter"
|
||||||
|
DefaultBrush="{DynamicResource DefaultTimelineIconForeground}"
|
||||||
|
ErrorBrush="{DynamicResource ErrorTimelineIconForeground}"
|
||||||
|
OngoingBrush="{DynamicResource OngoingTimelineIconForeground}"
|
||||||
|
SuccessBrush="{DynamicResource SuccessTimelineIconForeground}"
|
||||||
|
WarningBrush="{DynamicResource WarningTimelineIconForeground}" />
|
||||||
|
|
||||||
|
<ControlTheme x:Key="{x:Type u:TimelineItem}" TargetType="u:TimelineItem">
|
||||||
|
<Setter Property="u:TimelineItem.Template">
|
||||||
|
<ControlTemplate TargetType="u:TimelineItem">
|
||||||
|
<Grid ColumnDefinitions="Auto, *" RowDefinitions="*, Auto, *">
|
||||||
|
<Grid
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
RowDefinitions="Auto, Auto, *">
|
||||||
|
<Rectangle
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Width="1"
|
||||||
|
Height="8"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Classes="start"
|
||||||
|
Fill="LightGray" />
|
||||||
|
<Panel Grid.Row="1">
|
||||||
|
<Ellipse
|
||||||
|
Width="8"
|
||||||
|
Height="8"
|
||||||
|
Margin="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top">
|
||||||
|
<Ellipse.Fill>
|
||||||
|
<MultiBinding Converter="{StaticResource ForegroundConverter}">
|
||||||
|
<Binding Path="ItemType" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||||
|
<Binding Path="IconForeground" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Ellipse.Fill>
|
||||||
|
</Ellipse>
|
||||||
|
</Panel>
|
||||||
|
<Rectangle
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Width="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Classes="end"
|
||||||
|
Fill="LightGray" />
|
||||||
|
</Grid>
|
||||||
|
<Rectangle
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Width="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Classes="end"
|
||||||
|
Fill="LightGray" />
|
||||||
|
<ContentPresenter
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Foreground="Gray">
|
||||||
|
<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"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,0,0,16"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{TemplateBinding Content}"
|
||||||
|
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
<Style Selector="^:first /template/ Rectangle.start">
|
||||||
|
<Setter Property="Rectangle.Fill" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:last /template/ Rectangle.end">
|
||||||
|
<Setter Property="Rectangle.Fill" Value="Transparent" />
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace Ursa.Themes.Semi.Converters;
|
||||||
|
|
||||||
|
public class TimelineItemTypeToIconForegroundConverter: AvaloniaObject, IMultiValueConverter
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<IBrush> DefaultBrushProperty = AvaloniaProperty.Register<TimelineItemTypeToIconForegroundConverter, IBrush>(
|
||||||
|
nameof(DefaultBrush));
|
||||||
|
|
||||||
|
public IBrush DefaultBrush
|
||||||
|
{
|
||||||
|
get => GetValue(DefaultBrushProperty);
|
||||||
|
set => SetValue(DefaultBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> OngoingBrushProperty = AvaloniaProperty.Register<TimelineItemTypeToIconForegroundConverter, IBrush>(
|
||||||
|
nameof(OngoingBrush));
|
||||||
|
|
||||||
|
public IBrush OngoingBrush
|
||||||
|
{
|
||||||
|
get => GetValue(OngoingBrushProperty);
|
||||||
|
set => SetValue(OngoingBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> SuccessBrushProperty = AvaloniaProperty.Register<TimelineItemTypeToIconForegroundConverter, IBrush>(
|
||||||
|
nameof(SuccessBrush));
|
||||||
|
|
||||||
|
public IBrush SuccessBrush
|
||||||
|
{
|
||||||
|
get => GetValue(SuccessBrushProperty);
|
||||||
|
set => SetValue(SuccessBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> WarningBrushProperty = AvaloniaProperty.Register<TimelineItemTypeToIconForegroundConverter, IBrush>(
|
||||||
|
nameof(WarningBrush));
|
||||||
|
|
||||||
|
public IBrush WarningBrush
|
||||||
|
{
|
||||||
|
get => GetValue(WarningBrushProperty);
|
||||||
|
set => SetValue(WarningBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> ErrorBrushProperty = AvaloniaProperty.Register<TimelineItemTypeToIconForegroundConverter, IBrush>(
|
||||||
|
nameof(ErrorBrush));
|
||||||
|
|
||||||
|
public IBrush ErrorBrush
|
||||||
|
{
|
||||||
|
get => GetValue(ErrorBrushProperty);
|
||||||
|
set => SetValue(ErrorBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public object? Convert(IList<object?> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/Ursa.Themes.Semi/Themes/Light/Timeline.axaml
Normal file
8
src/Ursa.Themes.Semi/Themes/Light/Timeline.axaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<!-- Add Resources Here -->
|
||||||
|
<SolidColorBrush x:Key="DefaultTimelineIconForeground" Opacity="0.13" Color="#FF2E3238" />
|
||||||
|
<SolidColorBrush x:Key="OngoingTimelineIconForeground" Color="#FF0077FA" />
|
||||||
|
<SolidColorBrush x:Key="SuccessTimelineIconForeground" Color="#FF3BB346" />
|
||||||
|
<SolidColorBrush x:Key="WarningTimelineIconForeground" Color="#FFFC8800" />
|
||||||
|
<SolidColorBrush x:Key="ErrorTimelineIconForeground" Color="#FFF93920" />
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
<MergeResourceInclude Source="Banner.axaml" />
|
<MergeResourceInclude Source="Banner.axaml" />
|
||||||
<MergeResourceInclude Source="Divider.axaml" />
|
<MergeResourceInclude Source="Divider.axaml" />
|
||||||
<MergeResourceInclude Source="IPv4Box.axaml" />
|
<MergeResourceInclude Source="IPv4Box.axaml" />
|
||||||
|
<MergeResourceInclude Source="Timeline.axaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
56
src/Ursa/Controls/Timeline/Timeline.cs
Normal file
56
src/Ursa/Controls/Timeline/Timeline.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class Timeline: ItemsControl
|
||||||
|
{
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IDataTemplate?> ItemDescriptionTemplateProperty = AvaloniaProperty.Register<Timeline, IDataTemplate?>(
|
||||||
|
nameof(ItemDescriptionTemplate));
|
||||||
|
|
||||||
|
public IDataTemplate? ItemDescriptionTemplate
|
||||||
|
{
|
||||||
|
get => GetValue(ItemDescriptionTemplateProperty);
|
||||||
|
set => SetValue(ItemDescriptionTemplateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timeline()
|
||||||
|
{
|
||||||
|
ItemsView.CollectionChanged+=ItemsViewOnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ItemsViewOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
RefreshTimelineItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
RefreshTimelineItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshTimelineItems()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < this.LogicalChildren.Count; i++)
|
||||||
|
{
|
||||||
|
if (this.LogicalChildren[i] is TimelineItem t)
|
||||||
|
{
|
||||||
|
t.SetIndex(i == 0, i == this.LogicalChildren.Count - 1);
|
||||||
|
}
|
||||||
|
else if (this.LogicalChildren[i] is ContentPresenter { Child: TimelineItem t2 })
|
||||||
|
{
|
||||||
|
t2.SetIndex(i == 0, i == this.LogicalChildren.Count - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/Ursa/Controls/Timeline/TimelineItem.cs
Normal file
94
src/Ursa/Controls/Timeline/TimelineItem.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
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, PC_Default, PC_Ongoing, PC_Success, PC_Warning, PC_Error)]
|
||||||
|
public class TimelineItem: ContentControl
|
||||||
|
{
|
||||||
|
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<TimelineItemType, string> _itemTypeMapping = new Dictionary<TimelineItemType, string>
|
||||||
|
{
|
||||||
|
{TimelineItemType.Default, PC_Default},
|
||||||
|
{TimelineItemType.Ongoing, PC_Ongoing},
|
||||||
|
{TimelineItemType.Success, PC_Success},
|
||||||
|
{TimelineItemType.Warning, PC_Warning},
|
||||||
|
{TimelineItemType.Error, PC_Error},
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> IconForegroundProperty =
|
||||||
|
AvaloniaProperty.Register<TimelineItem, IBrush>(nameof(IconForeground));
|
||||||
|
|
||||||
|
public IBrush IconForeground
|
||||||
|
{
|
||||||
|
get => GetValue(IconForegroundProperty);
|
||||||
|
set => SetValue(IconForegroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<DateTime> TimeProperty = AvaloniaProperty.Register<TimelineItem, DateTime>(
|
||||||
|
nameof(Time));
|
||||||
|
public DateTime Time
|
||||||
|
{
|
||||||
|
get => GetValue(TimeProperty);
|
||||||
|
set => SetValue(TimeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string?> TimeFormatProperty = AvaloniaProperty.Register<TimelineItem, string?>(
|
||||||
|
nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern);
|
||||||
|
|
||||||
|
public string? TimeFormat
|
||||||
|
{
|
||||||
|
get => GetValue(TimeFormatProperty);
|
||||||
|
set => SetValue(TimeFormatProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IDataTemplate> DescriptionTemplateProperty = AvaloniaProperty.Register<TimelineItem, IDataTemplate>(
|
||||||
|
nameof(DescriptionTemplate));
|
||||||
|
|
||||||
|
public IDataTemplate DescriptionTemplate
|
||||||
|
{
|
||||||
|
get => GetValue(DescriptionTemplateProperty);
|
||||||
|
set => SetValue(DescriptionTemplateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<TimelineItemType> ItemTypeProperty = AvaloniaProperty.Register<TimelineItem, TimelineItemType>(
|
||||||
|
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<TimelineItem>((o, e) => { o.OnItemTypeChanged(e); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnItemTypeChanged(AvaloniaPropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
var oldValue = args.GetOldValue<TimelineItemType>();
|
||||||
|
var newValue = args.GetNewValue<TimelineItemType>();
|
||||||
|
PseudoClasses.Set(_itemTypeMapping[oldValue], false);
|
||||||
|
PseudoClasses.Set(_itemTypeMapping[newValue], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
src/Ursa/Controls/Timeline/TimelineItemType.cs
Normal file
10
src/Ursa/Controls/Timeline/TimelineItemType.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
|
public enum TimelineItemType
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Ongoing,
|
||||||
|
Success,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user