using System.Collections.Specialized; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Generators; 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)); [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 ContentMemberBindingProperty = AvaloniaProperty.Register( nameof(ContentMemberBinding)); [AssignBinding] [InheritDataTypeFromItems(nameof(ItemsSource))] public IBinding? ContentMemberBinding { get => GetValue(ContentMemberBindingProperty); set => SetValue(ContentMemberBindingProperty, value); } public static readonly StyledProperty IconTemplateProperty = AvaloniaProperty.Register( nameof(IconTemplate)); [InheritDataTypeFromItems(nameof(ItemsSource))] public IDataTemplate? IconTemplate { get => GetValue(IconTemplateProperty); set => SetValue(IconTemplateProperty, value); } public static readonly StyledProperty DescriptionTemplateProperty = AvaloniaProperty.Register( nameof(DescriptionTemplate)); [InheritDataTypeFromItems(nameof(ItemsSource))] public IDataTemplate? DescriptionTemplate { get => GetValue(DescriptionTemplateProperty); 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)); 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; SetItemMode(); } } protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { recycleKey = null; return item is not TimelineItem; } protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { if (item is TimelineItem t) return t; return new TimelineItem(); } protected override void PrepareContainerForItemOverride(Control container, object? item, int index) { 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 is not null) { t.Bind(TimelineItem.IconProperty, IconMemberBinding); } if (HeaderMemberBinding != null) { t.Bind(HeaderedContentControl.HeaderProperty, HeaderMemberBinding); } if (ContentMemberBinding != null) { t.Bind(ContentControl.ContentProperty, ContentMemberBinding); } if (TimeMemberBinding != null) { t.Bind(TimelineItem.TimeProperty, TimeMemberBinding); } t.SetIfUnset(TimelineItem.TimeFormatProperty, TimeFormat); t.SetIfUnset(TimelineItem.IconTemplateProperty, IconTemplate); t.SetIfUnset(HeaderedContentControl.HeaderTemplateProperty, ItemTemplate); t.SetIfUnset(ContentControl.ContentTemplateProperty, DescriptionTemplate); } } protected override Size ArrangeOverride(Size finalSize) { 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.PositionProperty, TimelineItemPosition.Left); } } else if (Mode == TimelineDisplayMode.Right) { foreach (var item in items) { SetIfUnset(item, TimelineItem.PositionProperty, TimelineItemPosition.Right); } } else if (Mode == TimelineDisplayMode.Center) { foreach (var item in items) { SetIfUnset(item, TimelineItem.PositionProperty, TimelineItemPosition.Separate); } } else if (Mode == TimelineDisplayMode.Alternate) { bool left = false; foreach (var item in items) { if (left) { SetIfUnset(item, TimelineItem.PositionProperty, TimelineItemPosition.Left); } else { SetIfUnset(item, TimelineItem.PositionProperty, TimelineItemPosition.Right); } left = !left; } } } } private void SetIfUnset(AvaloniaObject target, StyledProperty property, T value) { if (!target.IsSet(property)) target.SetCurrentValue(property, value); } }