wip: layout.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -4,37 +4,67 @@
|
||||
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:selectors="clr-namespace:Ursa.Demo.TemplateSelectors"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels"
|
||||
xmlns:selectors="clr-namespace:Ursa.Demo.TemplateSelectors"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:DataType="viewModels:TimelineDemoViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:TimelineDemoViewModel"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<selectors:TimelineIconTemplateSelector x:Key="IconSelector">
|
||||
<Ellipse x:Key="Default" Width="12" Height="12" Fill="{DynamicResource SemiGray6}"></Ellipse>
|
||||
<Ellipse x:Key="Ongoing" Width="12" Height="12" Fill="{DynamicResource SemiBlue6}"></Ellipse>
|
||||
<Ellipse x:Key="Success" Width="12" Height="12" Fill="{DynamicResource SemiGreen6}"></Ellipse>
|
||||
<Ellipse x:Key="Warning" Width="12" Height="12" Fill="{DynamicResource SemiOrange6}"></Ellipse>
|
||||
<Ellipse x:Key="Error" Width="12" Height="12" Fill="{DynamicResource SemiRed6}"></Ellipse>
|
||||
<Ellipse
|
||||
x:Key="Default"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Fill="{DynamicResource SemiGray6}" />
|
||||
<Ellipse
|
||||
x:Key="Ongoing"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Fill="{DynamicResource SemiBlue6}" />
|
||||
<Ellipse
|
||||
x:Key="Success"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Fill="{DynamicResource SemiGreen6}" />
|
||||
<Ellipse
|
||||
x:Key="Warning"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Fill="{DynamicResource SemiOrange6}" />
|
||||
<Ellipse
|
||||
x:Key="Error"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Fill="{DynamicResource SemiRed6}" />
|
||||
</selectors:TimelineIconTemplateSelector>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<StackPanel>
|
||||
<u:Timeline ItemsSource="{Binding Items}"
|
||||
HeaderMemberBinding="{ReflectionBinding Header}"
|
||||
DescriptionMemberBinding="{ReflectionBinding Description}"
|
||||
IconMemberBinding="{ReflectionBinding ItemType}"
|
||||
IconTemplate="{StaticResource IconSelector}"
|
||||
>
|
||||
</u:Timeline>
|
||||
<u:Timeline
|
||||
HorizontalAlignment="Left"
|
||||
DescriptionMemberBinding="{ReflectionBinding Description}"
|
||||
DisplayMemberBinding="{ReflectionBinding Header}"
|
||||
HeaderMemberBinding="{ReflectionBinding Header}"
|
||||
IconMemberBinding="{ReflectionBinding ItemType}"
|
||||
IconTemplate="{StaticResource IconSelector}"
|
||||
ItemsSource="{Binding Items}" />
|
||||
<u:Timeline>
|
||||
<u:TimelineItem Header="第一步" Content="Step 1" Type="Default"></u:TimelineItem>
|
||||
<u:TimelineItem Header="第二步" Content="Step 2" Type="Default"></u:TimelineItem>
|
||||
<u:TimelineItem Header="第三步" Content="Step 3" Type="Default"></u:TimelineItem>
|
||||
<u:TimelineItem
|
||||
Content="Step 1"
|
||||
Header="第一步"
|
||||
Type="Default" />
|
||||
<u:TimelineItem
|
||||
Content="Step 2"
|
||||
Header="第二步"
|
||||
Type="Default" />
|
||||
<u:TimelineItem
|
||||
Content="Step 3"
|
||||
Header="第三步"
|
||||
Type="Default" />
|
||||
</u:Timeline>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -26,48 +26,35 @@
|
||||
<converters:TimelineItemTypeToIconForegroundConverter
|
||||
x:Key="ForegroundConverter"
|
||||
DefaultBrush="{DynamicResource DefaultTimelineIconForeground}"
|
||||
ErrorBrush="{DynamicResource ErrorTimelineIconForeground}"
|
||||
OngoingBrush="{DynamicResource OngoingTimelineIconForeground}"
|
||||
SuccessBrush="{DynamicResource SuccessTimelineIconForeground}"
|
||||
WarningBrush="{DynamicResource WarningTimelineIconForeground}"
|
||||
ErrorBrush="{DynamicResource ErrorTimelineIconForeground}" />
|
||||
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 ColumnDefinitions="Auto, *, Auto" RowDefinitions="*, Auto, *">
|
||||
<Rectangle
|
||||
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="{DynamicResource TimelineLineBrush}" />
|
||||
<Panel Grid.Row="1">
|
||||
<ContentPresenter
|
||||
Content="{TemplateBinding Icon}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentTemplate="{TemplateBinding IconTemplate}"/>
|
||||
</Panel>
|
||||
<Rectangle
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Width="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes="end"
|
||||
Fill="{DynamicResource TimelineLineBrush}" />
|
||||
</Grid>
|
||||
Grid.Column="1"
|
||||
Width="1"
|
||||
Height="8"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Classes="start"
|
||||
Fill="{DynamicResource TimelineLineBrush}" />
|
||||
<ContentPresenter
|
||||
Name="PART_IconPresenter"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Icon}"
|
||||
ContentTemplate="{TemplateBinding IconTemplate}" />
|
||||
<Rectangle
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.Column="1"
|
||||
Width="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
@@ -80,8 +67,7 @@
|
||||
VerticalAlignment="Bottom"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
Foreground="Gray">
|
||||
</ContentPresenter>
|
||||
Foreground="Gray" />
|
||||
<ContentPresenter
|
||||
Name="content"
|
||||
Grid.Row="1"
|
||||
@@ -100,23 +86,12 @@
|
||||
<Style Selector="^:last /template/ Rectangle.end">
|
||||
<Setter Property="Rectangle.Fill" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="^:none /template/ Ellipse#PART_Indicator">
|
||||
<Setter Property="Ellipse.Fill" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=u:TimelineItem}, Path=IconForeground}" />
|
||||
</Style>
|
||||
<Style Selector="^:not(:none):default /template/ Ellipse#PART_Indicator">
|
||||
<Setter Property="Ellipse.Fill" Value="{DynamicResource DefaultTimelineIconForeground}" />
|
||||
</Style>
|
||||
<Style Selector="^:not(:none):ongoing /template/ Ellipse#PART_Indicator">
|
||||
<Setter Property="Ellipse.Fill" Value="{DynamicResource OngoingTimelineIconForeground}" />
|
||||
</Style>
|
||||
<Style Selector="^:not(:none):success /template/ Ellipse#PART_Indicator">
|
||||
<Setter Property="Ellipse.Fill" Value="{DynamicResource SuccessTimelineIconForeground}" />
|
||||
</Style>
|
||||
<Style Selector="^:not(:none):warning /template/ Ellipse#PART_Indicator">
|
||||
<Setter Property="Ellipse.Fill" Value="{DynamicResource WarningTimelineIconForeground}" />
|
||||
</Style>
|
||||
<Style Selector="^:not(:none):error /template/ Ellipse#PART_Indicator">
|
||||
<Setter Property="Ellipse.Fill" Value="{DynamicResource ErrorTimelineIconForeground}" />
|
||||
<Style Selector="^:empty-icon /template/ ContentPresenter#PART_IconPresenter">
|
||||
<Setter Property="Content">
|
||||
<Template>
|
||||
<Ellipse Width="8" Height="8" Fill="Gray"></Ellipse>
|
||||
</Template>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -6,12 +6,15 @@ 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<IBinding?> IconMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
|
||||
nameof(IconMemberBinding));
|
||||
|
||||
@@ -66,6 +69,29 @@ public class Timeline: ItemsControl
|
||||
set => SetValue(DescriptionTemplateProperty, 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)
|
||||
{
|
||||
panel.Mode = e.NewValue.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||
{
|
||||
recycleKey = null;
|
||||
|
||||
9
src/Ursa/Controls/Timeline/TimelineDisplayMode.cs
Normal file
9
src/Ursa/Controls/Timeline/TimelineDisplayMode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public enum TimelineDisplayMode
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Alternate,
|
||||
}
|
||||
@@ -8,9 +8,13 @@ using Avalonia.Media;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[PseudoClasses(":first", ":last")]
|
||||
[PseudoClasses(PC_First, PC_Last, PC_EmptyIcon)]
|
||||
public class TimelineItem: HeaderedContentControl
|
||||
{
|
||||
public const string PC_First = ":first";
|
||||
public const string PC_Last = ":last";
|
||||
public const string PC_EmptyIcon = ":empty-icon";
|
||||
|
||||
public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<TimelineItem, object?>(
|
||||
nameof(Icon));
|
||||
|
||||
@@ -37,10 +41,47 @@ public class TimelineItem: HeaderedContentControl
|
||||
get => GetValue(TypeProperty);
|
||||
set => SetValue(TypeProperty, 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);
|
||||
}
|
||||
|
||||
static TimelineItem()
|
||||
{
|
||||
IconProperty.Changed.AddClassHandler<TimelineItem, object?>((item, args) => { item.OnIconChanged(args); });
|
||||
}
|
||||
|
||||
private void OnIconChanged(AvaloniaPropertyChangedEventArgs<object?> args)
|
||||
{
|
||||
PseudoClasses.Set(PC_EmptyIcon, args.NewValue.Value is null);
|
||||
}
|
||||
|
||||
internal void SetEnd(bool start, bool end)
|
||||
{
|
||||
PseudoClasses.Set(":first", start);
|
||||
PseudoClasses.Set(":last", end);
|
||||
PseudoClasses.Set(PC_First, start);
|
||||
PseudoClasses.Set(PC_Last, end);
|
||||
}
|
||||
}
|
||||
43
src/Ursa/Controls/Timeline/TimelinePanel.cs
Normal file
43
src/Ursa/Controls/Timeline/TimelinePanel.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child is TimelineItem t)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
return base.MeasureOverride(availableSize);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
return base.ArrangeOverride(finalSize);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user