@@ -1,56 +1,220 @@
|
||||
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;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class Timeline: ItemsControl
|
||||
{
|
||||
private static readonly FuncTemplate<Panel?> DefaultPanel = new((Func<Panel>)(() => new TimelinePanel()));
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate?> ItemDescriptionTemplateProperty = AvaloniaProperty.Register<Timeline, IDataTemplate?>(
|
||||
nameof(ItemDescriptionTemplate));
|
||||
public static readonly StyledProperty<IBinding?> IconMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
|
||||
nameof(IconMemberBinding));
|
||||
|
||||
public IDataTemplate? ItemDescriptionTemplate
|
||||
[AssignBinding]
|
||||
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||
public IBinding? IconMemberBinding
|
||||
{
|
||||
get => GetValue(ItemDescriptionTemplateProperty);
|
||||
set => SetValue(ItemDescriptionTemplateProperty, value);
|
||||
get => GetValue(IconMemberBindingProperty);
|
||||
set => SetValue(IconMemberBindingProperty, value);
|
||||
}
|
||||
|
||||
public Timeline()
|
||||
public static readonly StyledProperty<IBinding?> HeaderMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
|
||||
nameof(HeaderMemberBinding));
|
||||
|
||||
[AssignBinding]
|
||||
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||
public IBinding? HeaderMemberBinding
|
||||
{
|
||||
ItemsView.CollectionChanged+=ItemsViewOnCollectionChanged;
|
||||
get => GetValue(HeaderMemberBindingProperty);
|
||||
set => SetValue(HeaderMemberBindingProperty, value);
|
||||
}
|
||||
|
||||
private void ItemsViewOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
public static readonly StyledProperty<IBinding?> ContentMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
|
||||
nameof(ContentMemberBinding));
|
||||
|
||||
[AssignBinding]
|
||||
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||
public IBinding? ContentMemberBinding
|
||||
{
|
||||
RefreshTimelineItems();
|
||||
get => GetValue(ContentMemberBindingProperty);
|
||||
set => SetValue(ContentMemberBindingProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate?> IconTemplateProperty = AvaloniaProperty.Register<Timeline, IDataTemplate?>(
|
||||
nameof(IconTemplate));
|
||||
|
||||
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||
public IDataTemplate? IconTemplate
|
||||
{
|
||||
get => GetValue(IconTemplateProperty);
|
||||
set => SetValue(IconTemplateProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
public static readonly StyledProperty<IDataTemplate?> DescriptionTemplateProperty = AvaloniaProperty.Register<Timeline, IDataTemplate?>(
|
||||
nameof(DescriptionTemplate));
|
||||
|
||||
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||
public IDataTemplate? DescriptionTemplate
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
RefreshTimelineItems();
|
||||
get => GetValue(DescriptionTemplateProperty);
|
||||
set => SetValue(DescriptionTemplateProperty, value);
|
||||
}
|
||||
|
||||
private void RefreshTimelineItems()
|
||||
public static readonly StyledProperty<IBinding?> TimeMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
|
||||
nameof(TimeMemberBinding));
|
||||
|
||||
[AssignBinding]
|
||||
[InheritDataTypeFromItems(nameof(ItemsSource))]
|
||||
public IBinding? TimeMemberBinding
|
||||
{
|
||||
for (int i = 0; i < this.LogicalChildren.Count; i++)
|
||||
get => GetValue(TimeMemberBindingProperty);
|
||||
set => SetValue(TimeMemberBindingProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> TimeFormatProperty = AvaloniaProperty.Register<Timeline, string?>(
|
||||
nameof(TimeFormat), defaultValue:"yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public string? TimeFormat
|
||||
{
|
||||
get => GetValue(TimeFormatProperty);
|
||||
set => SetValue(TimeFormatProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly StyledProperty<TimelineDisplayMode> ModeProperty = AvaloniaProperty.Register<Timeline, TimelineDisplayMode>(
|
||||
nameof(Mode));
|
||||
|
||||
public TimelineDisplayMode Mode
|
||||
{
|
||||
get => GetValue(ModeProperty);
|
||||
set => SetValue(ModeProperty, value);
|
||||
}
|
||||
|
||||
static Timeline()
|
||||
{
|
||||
ItemsPanelProperty.OverrideDefaultValue<Timeline>(DefaultPanel);
|
||||
ModeProperty.Changed.AddClassHandler<Timeline, TimelineDisplayMode>((t, e) => { t.OnDisplayModeChanged(e); });
|
||||
}
|
||||
|
||||
private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs<TimelineDisplayMode> e)
|
||||
{
|
||||
if (this.ItemsPanelRoot is TimelinePanel panel)
|
||||
{
|
||||
if (this.LogicalChildren[i] is TimelineItem t)
|
||||
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.SetIndex(i == 0, i == this.LogicalChildren.Count - 1);
|
||||
t.Bind(TimelineItem.IconProperty, IconMemberBinding);
|
||||
}
|
||||
else if (this.LogicalChildren[i] is ContentPresenter { Child: TimelineItem t2 })
|
||||
if (HeaderMemberBinding != null)
|
||||
{
|
||||
t2.SetIndex(i == 0, i == this.LogicalChildren.Count - 1);
|
||||
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<TimelineItem>();
|
||||
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<T>(AvaloniaObject target, StyledProperty<T> property, T value)
|
||||
{
|
||||
if (!target.IsSet(property))
|
||||
target.SetCurrentValue(property, value);
|
||||
}
|
||||
}
|
||||
22
src/Ursa/Controls/Timeline/TimelineDisplayMode.cs
Normal file
22
src/Ursa/Controls/Timeline/TimelineDisplayMode.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public enum TimelineDisplayMode
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Alternate,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Placement of timeline.
|
||||
/// Left means line is placed left to TimelineItem content.
|
||||
/// Right means line is placed right to TimelineItem content.
|
||||
/// Separate means line is placed between TimelineItem content and time.
|
||||
/// </summary>
|
||||
public enum TimelineItemPosition
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Separate,
|
||||
}
|
||||
@@ -1,45 +1,106 @@
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
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, PC_None)]
|
||||
public class TimelineItem: ContentControl
|
||||
[PseudoClasses(PC_First, PC_Last, PC_EmptyIcon, PC_AllLeft, PC_AllRight, PC_Separate)]
|
||||
[TemplatePart(PART_Header, typeof(ContentPresenter))]
|
||||
[TemplatePart(PART_Icon, typeof(Panel))]
|
||||
[TemplatePart(PART_Content, typeof(ContentPresenter))]
|
||||
[TemplatePart(PART_Time, typeof(TextBlock))]
|
||||
[TemplatePart(PART_RootGrid, typeof(Grid))]
|
||||
public class TimelineItem: HeaderedContentControl
|
||||
{
|
||||
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 const string PC_None = ":none";
|
||||
public const string PC_First = ":first";
|
||||
public const string PC_Last = ":last";
|
||||
public const string PC_EmptyIcon = ":empty-icon";
|
||||
public const string PC_AllLeft=":all-left";
|
||||
public const string PC_AllRight=":all-right";
|
||||
public const string PC_Separate = ":separate";
|
||||
public const string PART_Header = "PART_Header";
|
||||
public const string PART_Icon = "PART_Icon";
|
||||
public const string PART_Content = "PART_Content";
|
||||
public const string PART_Time = "PART_Time";
|
||||
public const string PART_RootGrid = "PART_RootGrid";
|
||||
|
||||
private ContentPresenter? _headerPresenter;
|
||||
private Panel? _iconPresenter;
|
||||
private ContentPresenter? _contentPresenter;
|
||||
private TextBlock? _timePresenter;
|
||||
private Grid? _rootGrid;
|
||||
|
||||
public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<TimelineItem, object?>(
|
||||
nameof(Icon));
|
||||
|
||||
private static readonly IReadOnlyDictionary<TimelineItemType, string> _itemTypeMapping = new Dictionary<TimelineItemType, string>
|
||||
public object? Icon
|
||||
{
|
||||
{TimelineItemType.Default, PC_Default},
|
||||
{TimelineItemType.Ongoing, PC_Ongoing},
|
||||
{TimelineItemType.Success, PC_Success},
|
||||
{TimelineItemType.Warning, PC_Warning},
|
||||
{TimelineItemType.Error, PC_Error},
|
||||
};
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> IconForegroundProperty =
|
||||
AvaloniaProperty.Register<TimelineItem, IBrush>(nameof(IconForeground));
|
||||
public static readonly StyledProperty<IDataTemplate?> IconTemplateProperty = AvaloniaProperty.Register<TimelineItem, IDataTemplate?>(
|
||||
nameof(IconTemplate));
|
||||
|
||||
public IBrush IconForeground
|
||||
public IDataTemplate? IconTemplate
|
||||
{
|
||||
get => GetValue(IconForegroundProperty);
|
||||
set => SetValue(IconForegroundProperty, value);
|
||||
get => GetValue(IconTemplateProperty);
|
||||
set => SetValue(IconTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TimelineItemType> TypeProperty = AvaloniaProperty.Register<TimelineItem, TimelineItemType>(
|
||||
nameof(Type));
|
||||
|
||||
public TimelineItemType Type
|
||||
{
|
||||
get => GetValue(TypeProperty);
|
||||
set => SetValue(TypeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TimelineItemPosition> PositionProperty = AvaloniaProperty.Register<TimelineItem, TimelineItemPosition>(
|
||||
nameof(Position), defaultValue: TimelineItemPosition.Right);
|
||||
|
||||
public TimelineItemPosition Position
|
||||
{
|
||||
get => GetValue(PositionProperty);
|
||||
set => SetValue(PositionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DirectProperty<TimelineItem, double> LeftWidthProperty = AvaloniaProperty.RegisterDirect<TimelineItem, double>(
|
||||
nameof(LeftWidth), o => o.LeftWidth, (o, v) => o.LeftWidth = v);
|
||||
private double _leftWidth;
|
||||
public double LeftWidth
|
||||
{
|
||||
get => _leftWidth;
|
||||
set => SetAndRaise(LeftWidthProperty, ref _leftWidth, value);
|
||||
}
|
||||
|
||||
public static readonly DirectProperty<TimelineItem, double> IconWidthProperty = AvaloniaProperty.RegisterDirect<TimelineItem, double>(
|
||||
nameof(IconWidth), o => o.IconWidth, (o, v) => o.IconWidth = v);
|
||||
private double _iconWidth;
|
||||
public double IconWidth
|
||||
{
|
||||
get => _iconWidth;
|
||||
set => SetAndRaise(IconWidthProperty, ref _iconWidth, value);
|
||||
}
|
||||
|
||||
public static readonly DirectProperty<TimelineItem, double> RightWidthProperty = AvaloniaProperty.RegisterDirect<TimelineItem, double>(
|
||||
nameof(RightWidth), o => o.RightWidth, (o, v) => o.RightWidth = v);
|
||||
private double _rightWidth;
|
||||
public double RightWidth
|
||||
{
|
||||
get => _rightWidth;
|
||||
set => SetAndRaise(RightWidthProperty, ref _rightWidth, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<DateTime> TimeProperty = AvaloniaProperty.Register<TimelineItem, DateTime>(
|
||||
nameof(Time));
|
||||
|
||||
public DateTime Time
|
||||
{
|
||||
get => GetValue(TimeProperty);
|
||||
@@ -47,7 +108,7 @@ public class TimelineItem: ContentControl
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> TimeFormatProperty = AvaloniaProperty.Register<TimelineItem, string?>(
|
||||
nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern);
|
||||
nameof(TimeFormat));
|
||||
|
||||
public string? TimeFormat
|
||||
{
|
||||
@@ -55,47 +116,86 @@ public class TimelineItem: ContentControl
|
||||
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); });
|
||||
IconForegroundProperty.Changed.AddClassHandler<TimelineItem>((o, e) => { o.OnIconForegroundChanged(e); });
|
||||
IconProperty.Changed.AddClassHandler<TimelineItem, object?>((item, args) => { item.OnIconChanged(args); });
|
||||
PositionProperty.Changed.AddClassHandler<TimelineItem, TimelineItemPosition>((item, args) => { item.OnModeChanged(args); });
|
||||
AffectsMeasure<TimelineItem>(LeftWidthProperty, RightWidthProperty, IconWidthProperty);
|
||||
}
|
||||
|
||||
private void OnItemTypeChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
private void OnModeChanged(AvaloniaPropertyChangedEventArgs<TimelineItemPosition> args)
|
||||
{
|
||||
var oldValue = args.GetOldValue<TimelineItemType>();
|
||||
var newValue = args.GetNewValue<TimelineItemType>();
|
||||
PseudoClasses.Set(_itemTypeMapping[oldValue], false);
|
||||
PseudoClasses.Set(_itemTypeMapping[newValue], true);
|
||||
SetMode(args.NewValue.Value);
|
||||
}
|
||||
|
||||
private void OnIconForegroundChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
private void SetMode(TimelineItemPosition mode)
|
||||
{
|
||||
IBrush? newValue = args.GetOldValue<IBrush?>();
|
||||
PseudoClasses.Set(PC_None, newValue is null);
|
||||
PseudoClasses.Set(PC_AllLeft, mode == TimelineItemPosition.Left);
|
||||
PseudoClasses.Set(PC_AllRight, mode == TimelineItemPosition.Right);
|
||||
PseudoClasses.Set(PC_Separate, mode == TimelineItemPosition.Separate);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
_rootGrid = e.NameScope.Find<Grid>(PART_RootGrid);
|
||||
_headerPresenter = e.NameScope.Find<ContentPresenter>(PART_Header);
|
||||
_iconPresenter = e.NameScope.Find<Panel>(PART_Icon);
|
||||
_contentPresenter = e.NameScope.Find<ContentPresenter>(PART_Content);
|
||||
_timePresenter = e.NameScope.Find<TextBlock>(PART_Time);
|
||||
PseudoClasses.Set(PC_EmptyIcon, Icon is null);
|
||||
SetMode(Position);
|
||||
}
|
||||
|
||||
private void OnIconChanged(AvaloniaPropertyChangedEventArgs<object?> args)
|
||||
{
|
||||
PseudoClasses.Set(PC_EmptyIcon, args.NewValue.Value is null);
|
||||
}
|
||||
|
||||
internal void SetEnd(bool start, bool end)
|
||||
{
|
||||
PseudoClasses.Set(PC_First, start);
|
||||
PseudoClasses.Set(PC_Last, end);
|
||||
}
|
||||
|
||||
internal (double left, double mid, double right) GetWidth()
|
||||
{
|
||||
if (_headerPresenter is null) return new ValueTuple<double, double, double>(0, 0, 0);
|
||||
double header = _headerPresenter?.DesiredSize.Width ?? 0;
|
||||
double icon = _iconPresenter?.DesiredSize.Width ?? 0;
|
||||
double content = _contentPresenter?.DesiredSize.Width ?? 0;
|
||||
double time = _timePresenter?.DesiredSize.Width ?? 0;
|
||||
double max = Math.Max(header, content);
|
||||
if (Position == TimelineItemPosition.Left)
|
||||
{
|
||||
max = Math.Max(max, time);
|
||||
return (0, icon, max);
|
||||
}
|
||||
if (Position == TimelineItemPosition.Right)
|
||||
{
|
||||
max = Math.Max(max, time);
|
||||
return (max , icon, 0);
|
||||
}
|
||||
if (Position == TimelineItemPosition.Separate)
|
||||
{
|
||||
return (time, icon, max);
|
||||
}
|
||||
return new ValueTuple<double, double, double>(0, 0, 0);
|
||||
}
|
||||
|
||||
internal void SetWidth(double? left, double? mid, double? right)
|
||||
{
|
||||
if (_rootGrid is null) return;
|
||||
_rootGrid.ColumnDefinitions[0].Width = new GridLength(left??0);
|
||||
_rootGrid.ColumnDefinitions[1].Width = new GridLength(mid??0);
|
||||
_rootGrid.ColumnDefinitions[2].Width = new GridLength(right??0);
|
||||
}
|
||||
|
||||
internal void SetIfUnset<T>(AvaloniaProperty<T> property, T value)
|
||||
{
|
||||
if (!IsSet(property))
|
||||
{
|
||||
SetCurrentValue(property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/Ursa/Controls/Timeline/TimelinePanel.cs
Normal file
75
src/Ursa/Controls/Timeline/TimelinePanel.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class TimelinePanel: Panel
|
||||
{
|
||||
public static readonly StyledProperty<TimelineDisplayMode> ModeProperty =
|
||||
Timeline.ModeProperty.AddOwner<TimelinePanel>();
|
||||
|
||||
public TimelineDisplayMode Mode
|
||||
{
|
||||
get => GetValue(ModeProperty);
|
||||
set => SetValue(ModeProperty, value);
|
||||
}
|
||||
|
||||
static TimelinePanel()
|
||||
{
|
||||
AffectsMeasure<TimelinePanel>(ModeProperty);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
double left = 0;
|
||||
double right = 0;
|
||||
double icon = 0;
|
||||
double height = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Measure(availableSize);
|
||||
if (child is TimelineItem t)
|
||||
{
|
||||
var doubles = t.GetWidth();
|
||||
left = Math.Max(left, doubles.left);
|
||||
icon = Math.Max(icon, doubles.mid);
|
||||
right = Math.Max(right, doubles.right);
|
||||
}
|
||||
height+=child.DesiredSize.Height;
|
||||
}
|
||||
return new Size(left+icon+right, height);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
|
||||
double left = 0, mid = 0, right = 0;
|
||||
double height = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child is TimelineItem t)
|
||||
{
|
||||
var doubles = t.GetWidth();
|
||||
left = Math.Max(left, doubles.left);
|
||||
mid = Math.Max(mid, doubles.mid);
|
||||
right = Math.Max(right, doubles.right);
|
||||
}
|
||||
}
|
||||
|
||||
Rect rect = new Rect(0, 0, left + mid + right, 0);
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child is TimelineItem t)
|
||||
{
|
||||
t.SetWidth(left, mid, right);
|
||||
t.InvalidateArrange();
|
||||
rect = rect.WithHeight(t.DesiredSize.Height);
|
||||
child.Arrange(rect);
|
||||
rect = rect.WithY(rect.Y + t.DesiredSize.Height);
|
||||
height+=t.DesiredSize.Height;
|
||||
}
|
||||
}
|
||||
return new Size(left + mid + right, height);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user