WIP: layout with new panel.

This commit is contained in:
rabbitism
2024-01-04 20:33:42 +08:00
parent 1d6d8feaec
commit 66bc512ae2
7 changed files with 250 additions and 47 deletions

View File

@@ -47,23 +47,26 @@
<u:Timeline <u:Timeline
HorizontalAlignment="Left" HorizontalAlignment="Left"
DescriptionMemberBinding="{ReflectionBinding Description}" DescriptionMemberBinding="{ReflectionBinding Description}"
DisplayMemberBinding="{ReflectionBinding Header}"
HeaderMemberBinding="{ReflectionBinding Header}" HeaderMemberBinding="{ReflectionBinding Header}"
IconMemberBinding="{ReflectionBinding ItemType}" IconMemberBinding="{ReflectionBinding ItemType}"
IconTemplate="{StaticResource IconSelector}" IconTemplate="{StaticResource IconSelector}"
ItemsSource="{Binding Items}" /> ItemsSource="{Binding Items}"
<u:Timeline> TimeMemberBinding="{ReflectionBinding Time}" />
<u:Timeline HorizontalAlignment="Left">
<u:TimelineItem <u:TimelineItem
Content="Step 1" Content="Step 1"
Header="第一步" Header="第一步"
Mode="Left"
Type="Default" /> Type="Default" />
<u:TimelineItem <u:TimelineItem
Content="Step 2" Content="Step 2"
Header="第二步" Header="第二步"
Mode="Right"
Type="Default" /> Type="Default" />
<u:TimelineItem <u:TimelineItem
Content="Step 3" Content="Step 3"
Header="第三步" Header="第三步"
Mode="Separate"
Type="Default" /> Type="Default" />
</u:Timeline> </u:Timeline>
</StackPanel> </StackPanel>

View File

@@ -11,7 +11,6 @@ public class TimelineDemoViewModel: ViewModelBase
new() new()
{ {
Time = DateTime.Now, Time = DateTime.Now,
TimeFormat = "yyyy-MM-dd HH:mm:ss",
Description = "Item 1", Description = "Item 1",
Header = "审核中", Header = "审核中",
ItemType = TimelineItemType.Success, ItemType = TimelineItemType.Success,
@@ -19,7 +18,6 @@ public class TimelineDemoViewModel: ViewModelBase
new() new()
{ {
Time = DateTime.Now, Time = DateTime.Now,
TimeFormat = "HH:mm:ss",
Description = "Item 2", Description = "Item 2",
Header = "发布成功", Header = "发布成功",
ItemType = TimelineItemType.Ongoing, ItemType = TimelineItemType.Ongoing,
@@ -27,7 +25,6 @@ public class TimelineDemoViewModel: ViewModelBase
new() new()
{ {
Time = DateTime.Now, Time = DateTime.Now,
TimeFormat = "HH:mm:ss",
Description = "Item 3", Description = "Item 3",
Header = "审核失败", Header = "审核失败",
ItemType = TimelineItemType.Error, ItemType = TimelineItemType.Error,

View File

@@ -18,7 +18,7 @@
<ControlTheme x:Key="{x:Type u:Timeline}" TargetType="u:Timeline"> <ControlTheme x:Key="{x:Type u:Timeline}" TargetType="u:Timeline">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="u:Timeline"> <ControlTemplate TargetType="u:Timeline">
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" /> <ItemsPresenter Grid.IsSharedSizeScope="True" ItemsPanel="{TemplateBinding ItemsPanel}" />
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</ControlTheme> </ControlTheme>
@@ -32,51 +32,64 @@
WarningBrush="{DynamicResource WarningTimelineIconForeground}" /> WarningBrush="{DynamicResource WarningTimelineIconForeground}" />
<ControlTheme x:Key="{x:Type u:TimelineItem}" TargetType="u:TimelineItem"> <ControlTheme x:Key="{x:Type u:TimelineItem}" TargetType="u:TimelineItem">
<Setter Property="HorizontalAlignment" Value="Left"></Setter>
<Setter Property="VerticalAlignment" Value="Top"></Setter>
<Setter Property="u:TimelineItem.Template"> <Setter Property="u:TimelineItem.Template">
<ControlTemplate TargetType="u:TimelineItem"> <ControlTemplate TargetType="u:TimelineItem">
<Grid ColumnDefinitions="Auto, *, Auto" RowDefinitions="*, Auto, *"> <Grid Name="PART_RootGrid"
<Rectangle RowDefinitions="Auto, Auto, Auto"
ColumnDefinitions="Auto, Auto, Auto">
<!-- Icon and Axis -->
<Grid
Name="PART_IconAxisRoot"
Grid.Row="0" Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1" Grid.Column="1"
Width="1" RowDefinitions="Auto, *">
Height="8" <ContentPresenter
HorizontalAlignment="Center" Name="PART_Icon"
VerticalAlignment="Top" Grid.Row="0"
Classes="start" Margin="8"
Fill="{DynamicResource TimelineLineBrush}" /> HorizontalAlignment="Center"
<ContentPresenter VerticalAlignment="Center"
Name="PART_IconPresenter" Content="{TemplateBinding Icon}"
Grid.Row="1" ContentTemplate="{TemplateBinding IconTemplate}" />
Grid.Column="1" <Rectangle
HorizontalAlignment="Center" Grid.Row="1"
VerticalAlignment="Center" Width="1"
Content="{TemplateBinding Icon}" HorizontalAlignment="Center"
ContentTemplate="{TemplateBinding IconTemplate}" /> VerticalAlignment="Stretch"
<Rectangle Classes="end"
Grid.Row="2" Fill="{DynamicResource TimelineLineBrush}" />
Grid.Column="1" </Grid>
Width="1"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Classes="end"
Fill="{DynamicResource TimelineLineBrush}" />
<ContentPresenter <ContentPresenter
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="2"
HorizontalAlignment="Stretch" Name="PART_Header"
VerticalAlignment="Bottom" Margin="8 4"
Content="{TemplateBinding Header}" Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplate="{TemplateBinding HeaderTemplate}" />
Foreground="Gray" />
<ContentPresenter <ContentPresenter
Name="content"
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="2"
Margin="0,0,0,16" Name="PART_Content"
HorizontalAlignment="Left" Margin="8 2"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" /> ContentTemplate="{TemplateBinding ContentTemplate}" />
<TextBlock
Grid.Row="0"
Grid.Column="0"
Name="PART_Time"
Margin="8 2"
TextWrapping="Wrap"
>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource FormatConverter}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Time"></Binding>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="TimeFormat"></Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
@@ -86,12 +99,57 @@
<Style Selector="^:last /template/ Rectangle.end"> <Style Selector="^:last /template/ Rectangle.end">
<Setter Property="Rectangle.Fill" Value="Transparent" /> <Setter Property="Rectangle.Fill" Value="Transparent" />
</Style> </Style>
<Style Selector="^:empty-icon /template/ ContentPresenter#PART_IconPresenter"> <Style Selector="^:empty-icon /template/ ContentPresenter#PART_Icon">
<Setter Property="Content"> <Setter Property="Content">
<Template> <Template>
<Ellipse Width="8" Height="8" Fill="Gray"></Ellipse> <Ellipse
Width="8"
Height="8"
Fill="Gray" />
</Template> </Template>
</Setter> </Setter>
</Style> </Style>
<Style Selector="^:all-left">
<Style Selector="^ /template/ ContentPresenter#PART_Header">
<Setter Property="Grid.Row" Value="0"></Setter>
<Setter Property="Grid.Column" Value="0"></Setter>
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_Content">
<Setter Property="Grid.Row" Value="1"></Setter>
<Setter Property="Grid.Column" Value="0"></Setter>
</Style>
<Style Selector="^ /template/ TextBlock#PART_Time">
<Setter Property="Grid.Row" Value="2"></Setter>
<Setter Property="Grid.Column" Value="0"></Setter>
</Style>
</Style>
<Style Selector="^:all-right">
<Style Selector="^ /template/ ContentPresenter#PART_Header">
<Setter Property="Grid.Row" Value="0"></Setter>
<Setter Property="Grid.Column" Value="2"></Setter>
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_Content">
<Setter Property="Grid.Row" Value="1"></Setter>
<Setter Property="Grid.Column" Value="2"></Setter>
</Style>
<Style Selector="^ /template/ TextBlock#PART_Time">
<Setter Property="Grid.Row" Value="2"></Setter>
<Setter Property="Grid.Column" Value="2"></Setter>
</Style>
</Style>
<Style Selector="^:separate">
<Style Selector="^ /template/ ContentPresenter#PART_Header">
<Setter Property="Grid.Row" Value="0"></Setter>
<Setter Property="Grid.Column" Value="2"></Setter>
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_Content">
<Setter Property="Grid.Row" Value="1"></Setter>
<Setter Property="Grid.Column" Value="2"></Setter>
</Style>
<Style Selector="^ /template/ TextBlock#PART_Time">
<Setter Property="Grid.Row" Value="0"></Setter>
<Setter Property="Grid.Column" Value="0"></Setter>
</Style>
</Style>
</ControlTheme> </ControlTheme>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -69,6 +69,27 @@ public class Timeline: ItemsControl
set => SetValue(DescriptionTemplateProperty, value); set => SetValue(DescriptionTemplateProperty, value);
} }
public static readonly StyledProperty<IBinding?> TimeMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
nameof(TimeMemberBinding));
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IBinding? TimeMemberBinding
{
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>( public static readonly StyledProperty<TimelineDisplayMode> ModeProperty = AvaloniaProperty.Register<Timeline, TimelineDisplayMode>(
nameof(Mode)); nameof(Mode));
@@ -124,6 +145,11 @@ public class Timeline: ItemsControl
{ {
t.Bind(ContentControl.ContentProperty, DescriptionMemberBinding); t.Bind(ContentControl.ContentProperty, DescriptionMemberBinding);
} }
if (TimeMemberBinding != null)
{
t.Bind(TimelineItem.TimeProperty, TimeMemberBinding);
}
t.SetCurrentValue(TimelineItem.TimeFormatProperty, TimeFormat);
t.SetCurrentValue(TimelineItem.IconTemplateProperty, IconTemplate); t.SetCurrentValue(TimelineItem.IconTemplateProperty, IconTemplate);
t.SetCurrentValue(HeaderedContentControl.HeaderTemplateProperty, ItemTemplate); t.SetCurrentValue(HeaderedContentControl.HeaderTemplateProperty, ItemTemplate);
t.SetCurrentValue(ContentControl.ContentTemplateProperty, DescriptionTemplate); t.SetCurrentValue(ContentControl.ContentTemplateProperty, DescriptionTemplate);

View File

@@ -6,4 +6,11 @@ public enum TimelineDisplayMode
Center, Center,
Right, Right,
Alternate, Alternate,
}
public enum TimelineItemDisplayMode
{
Left,
Right,
Separate,
} }

View File

@@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
@@ -9,11 +10,30 @@ using Avalonia.Media;
namespace Ursa.Controls; namespace Ursa.Controls;
[PseudoClasses(PC_First, PC_Last, PC_EmptyIcon)] [PseudoClasses(PC_First, PC_Last, PC_EmptyIcon)]
[TemplatePart(PART_Header, typeof(ContentPresenter))]
[TemplatePart(PART_Icon, typeof(ContentPresenter))]
[TemplatePart(PART_Content, typeof(ContentPresenter))]
[TemplatePart(PART_Time, typeof(TextBlock))]
[TemplatePart(PART_RootGrid, typeof(Grid))]
public class TimelineItem: HeaderedContentControl public class TimelineItem: HeaderedContentControl
{ {
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 const string PC_EmptyIcon = ":empty-icon"; 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 ContentPresenter? _iconPresenter;
private ContentPresenter? _contentPresenter;
private TextBlock? _timePresenter;
private Grid? _rootGrid;
public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<TimelineItem, object?>( public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<TimelineItem, object?>(
nameof(Icon)); nameof(Icon));
@@ -41,6 +61,15 @@ public class TimelineItem: HeaderedContentControl
get => GetValue(TypeProperty); get => GetValue(TypeProperty);
set => SetValue(TypeProperty, value); set => SetValue(TypeProperty, value);
} }
public static readonly StyledProperty<TimelineItemDisplayMode> ModeProperty = AvaloniaProperty.Register<TimelineItem, TimelineItemDisplayMode>(
nameof(Mode), defaultValue: TimelineItemDisplayMode.Right);
public TimelineItemDisplayMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
public static readonly DirectProperty<TimelineItem, double> LeftWidthProperty = AvaloniaProperty.RegisterDirect<TimelineItem, double>( public static readonly DirectProperty<TimelineItem, double> LeftWidthProperty = AvaloniaProperty.RegisterDirect<TimelineItem, double>(
nameof(LeftWidth), o => o.LeftWidth, (o, v) => o.LeftWidth = v); nameof(LeftWidth), o => o.LeftWidth, (o, v) => o.LeftWidth = v);
@@ -69,9 +98,53 @@ public class TimelineItem: HeaderedContentControl
set => SetAndRaise(RightWidthProperty, ref _rightWidth, value); set => SetAndRaise(RightWidthProperty, ref _rightWidth, 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));
public string? TimeFormat
{
get => GetValue(TimeFormatProperty);
set => SetValue(TimeFormatProperty, value);
}
static TimelineItem() static TimelineItem()
{ {
IconProperty.Changed.AddClassHandler<TimelineItem, object?>((item, args) => { item.OnIconChanged(args); }); IconProperty.Changed.AddClassHandler<TimelineItem, object?>((item, args) => { item.OnIconChanged(args); });
ModeProperty.Changed.AddClassHandler<TimelineItem, TimelineItemDisplayMode>((item, args) => { item.OnModeChanged(args); });
AffectsMeasure<TimelineItem>(LeftWidthProperty, RightWidthProperty, IconWidthProperty);
}
private void OnModeChanged(AvaloniaPropertyChangedEventArgs<TimelineItemDisplayMode> args)
{
SetMode(args.NewValue.Value);
}
private void SetMode(TimelineItemDisplayMode mode)
{
PseudoClasses.Set(PC_AllLeft, mode == TimelineItemDisplayMode.Left);
PseudoClasses.Set(PC_AllRight, mode == TimelineItemDisplayMode.Right);
PseudoClasses.Set(PC_Separate, mode == TimelineItemDisplayMode.Separate);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
PseudoClasses.Set(PC_EmptyIcon, Icon is null);
_headerPresenter = e.NameScope.Find<ContentPresenter>(PART_Header);
_iconPresenter = e.NameScope.Find<ContentPresenter>(PART_Icon);
_contentPresenter = e.NameScope.Find<ContentPresenter>(PART_Content);
_timePresenter = e.NameScope.Find<TextBlock>(PART_Time);
_rootGrid = e.NameScope.Find<Grid>(PART_RootGrid);
SetMode(Mode);
} }
private void OnIconChanged(AvaloniaPropertyChangedEventArgs<object?> args) private void OnIconChanged(AvaloniaPropertyChangedEventArgs<object?> args)
@@ -84,4 +157,14 @@ public class TimelineItem: HeaderedContentControl
PseudoClasses.Set(PC_First, start); PseudoClasses.Set(PC_First, start);
PseudoClasses.Set(PC_Last, end); PseudoClasses.Set(PC_Last, end);
} }
internal (double?, double?, double?, double?) GetWidth()
{
return (_headerPresenter?.Bounds.Width, _contentPresenter?.Bounds.Width, _iconPresenter?.Bounds.Width, _timePresenter?.Bounds.Width);
}
internal void SetWidth(double? header, double? content, double? icon, double? time)
{
_rootGrid.ColumnDefinitions[0].Width = new GridLength(200);
}
} }

View File

@@ -25,19 +25,48 @@ public class TimelinePanel: Panel
double left = 0; double left = 0;
double right = 0; double right = 0;
double icon = 0; double icon = 0;
double width = 0;
double height = 0;
foreach (var child in Children)
{
child.Measure(availableSize);
if (child is TimelineItem t)
{
var doubles = t.GetWidth();
}
width = Math.Max(width, child.DesiredSize.Width);
height+=child.DesiredSize.Height;
}
foreach (var child in Children) foreach (var child in Children)
{ {
if (child is TimelineItem t) if (child is TimelineItem t)
{ {
t.LeftWidth = left;
t.RightWidth = right;
t.IconWidth = icon;
} }
} }
return base.MeasureOverride(availableSize);
return new Size(width, height);
} }
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
return base.ArrangeOverride(finalSize); Rect rect = new Rect();
foreach (var child in Children)
{
rect = rect.WithWidth(Math.Max(rect.Width, child.DesiredSize.Width));
rect = rect.WithHeight(rect.Height + child.DesiredSize.Height);
child.Arrange(rect);
rect = rect.WithY(rect.Y+child.DesiredSize.Height);
if (child is TimelineItem t)
{
var doubles = t.GetWidth();
t.SetWidth(0, 0, 0, 0);
}
}
//return base.ArrangeOverride(finalSize);
return rect.Size;
} }
} }