wip: layout.

This commit is contained in:
rabbitism
2024-01-02 22:32:25 +08:00
parent d5b5792870
commit 1d6d8feaec
7 changed files with 197 additions and 72 deletions

View File

@@ -6,6 +6,7 @@
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -4,37 +4,67 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:selectors="clr-namespace:Ursa.Demo.TemplateSelectors"
xmlns:u="https://irihi.tech/ursa" xmlns:u="https://irihi.tech/ursa"
xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels" xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels"
xmlns:selectors="clr-namespace:Ursa.Demo.TemplateSelectors"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
x:DataType="viewModels:TimelineDemoViewModel"
x:CompileBindings="True" x:CompileBindings="True"
x:DataType="viewModels:TimelineDemoViewModel"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
<selectors:TimelineIconTemplateSelector x:Key="IconSelector"> <selectors:TimelineIconTemplateSelector x:Key="IconSelector">
<Ellipse x:Key="Default" Width="12" Height="12" Fill="{DynamicResource SemiGray6}"></Ellipse> <Ellipse
<Ellipse x:Key="Ongoing" Width="12" Height="12" Fill="{DynamicResource SemiBlue6}"></Ellipse> x:Key="Default"
<Ellipse x:Key="Success" Width="12" Height="12" Fill="{DynamicResource SemiGreen6}"></Ellipse> Width="12"
<Ellipse x:Key="Warning" Width="12" Height="12" Fill="{DynamicResource SemiOrange6}"></Ellipse> Height="12"
<Ellipse x:Key="Error" Width="12" Height="12" Fill="{DynamicResource SemiRed6}"></Ellipse> 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> </selectors:TimelineIconTemplateSelector>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<StackPanel> <StackPanel>
<u:Timeline ItemsSource="{Binding Items}" <u:Timeline
HeaderMemberBinding="{ReflectionBinding Header}" HorizontalAlignment="Left"
DescriptionMemberBinding="{ReflectionBinding Description}" DescriptionMemberBinding="{ReflectionBinding Description}"
IconMemberBinding="{ReflectionBinding ItemType}" DisplayMemberBinding="{ReflectionBinding Header}"
IconTemplate="{StaticResource IconSelector}" HeaderMemberBinding="{ReflectionBinding Header}"
> IconMemberBinding="{ReflectionBinding ItemType}"
</u:Timeline> IconTemplate="{StaticResource IconSelector}"
ItemsSource="{Binding Items}" />
<u:Timeline> <u:Timeline>
<u:TimelineItem Header="第一步" Content="Step 1" Type="Default"></u:TimelineItem> <u:TimelineItem
<u:TimelineItem Header="第二步" Content="Step 2" Type="Default"></u:TimelineItem> Content="Step 1"
<u:TimelineItem Header="第步" Content="Step 3" Type="Default"></u:TimelineItem> Header="第步"
Type="Default" />
<u:TimelineItem
Content="Step 2"
Header="第二步"
Type="Default" />
<u:TimelineItem
Content="Step 3"
Header="第三步"
Type="Default" />
</u:Timeline> </u:Timeline>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -26,48 +26,35 @@
<converters:TimelineItemTypeToIconForegroundConverter <converters:TimelineItemTypeToIconForegroundConverter
x:Key="ForegroundConverter" x:Key="ForegroundConverter"
DefaultBrush="{DynamicResource DefaultTimelineIconForeground}" DefaultBrush="{DynamicResource DefaultTimelineIconForeground}"
ErrorBrush="{DynamicResource ErrorTimelineIconForeground}"
OngoingBrush="{DynamicResource OngoingTimelineIconForeground}" OngoingBrush="{DynamicResource OngoingTimelineIconForeground}"
SuccessBrush="{DynamicResource SuccessTimelineIconForeground}" SuccessBrush="{DynamicResource SuccessTimelineIconForeground}"
WarningBrush="{DynamicResource WarningTimelineIconForeground}" WarningBrush="{DynamicResource WarningTimelineIconForeground}" />
ErrorBrush="{DynamicResource ErrorTimelineIconForeground}" />
<ControlTheme x:Key="{x:Type u:TimelineItem}" TargetType="u:TimelineItem"> <ControlTheme x:Key="{x:Type u:TimelineItem}" TargetType="u:TimelineItem">
<Setter Property="u:TimelineItem.Template"> <Setter Property="u:TimelineItem.Template">
<ControlTemplate TargetType="u:TimelineItem"> <ControlTemplate TargetType="u:TimelineItem">
<Grid ColumnDefinitions="Auto, *" RowDefinitions="*, Auto, *"> <Grid ColumnDefinitions="Auto, *, Auto" RowDefinitions="*, Auto, *">
<Grid <Rectangle
Grid.Row="0" Grid.Row="0"
Grid.RowSpan="2" Grid.Column="1"
Grid.Column="0" Width="1"
RowDefinitions="Auto, Auto, *"> Height="8"
<Rectangle HorizontalAlignment="Center"
Grid.Row="0" VerticalAlignment="Top"
Grid.Column="0" Classes="start"
Width="1" Fill="{DynamicResource TimelineLineBrush}" />
Height="8" <ContentPresenter
HorizontalAlignment="Center" Name="PART_IconPresenter"
VerticalAlignment="Top" Grid.Row="1"
Classes="start" Grid.Column="1"
Fill="{DynamicResource TimelineLineBrush}" /> HorizontalAlignment="Center"
<Panel Grid.Row="1"> VerticalAlignment="Center"
<ContentPresenter Content="{TemplateBinding Icon}"
Content="{TemplateBinding Icon}" ContentTemplate="{TemplateBinding IconTemplate}" />
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>
<Rectangle <Rectangle
Grid.Row="2" Grid.Row="2"
Grid.Column="0" Grid.Column="1"
Width="1" Width="1"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
@@ -80,8 +67,7 @@
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Content="{TemplateBinding Header}" Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="Gray"> Foreground="Gray" />
</ContentPresenter>
<ContentPresenter <ContentPresenter
Name="content" Name="content"
Grid.Row="1" Grid.Row="1"
@@ -100,23 +86,12 @@
<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="^:none /template/ Ellipse#PART_Indicator"> <Style Selector="^:empty-icon /template/ ContentPresenter#PART_IconPresenter">
<Setter Property="Ellipse.Fill" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=u:TimelineItem}, Path=IconForeground}" /> <Setter Property="Content">
</Style> <Template>
<Style Selector="^:not(:none):default /template/ Ellipse#PART_Indicator"> <Ellipse Width="8" Height="8" Fill="Gray"></Ellipse>
<Setter Property="Ellipse.Fill" Value="{DynamicResource DefaultTimelineIconForeground}" /> </Template>
</Style> </Setter>
<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> </Style>
</ControlTheme> </ControlTheme>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -6,12 +6,15 @@ 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;
using Avalonia.Layout;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Ursa.Controls; namespace Ursa.Controls;
public class Timeline: ItemsControl 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?>( public static readonly StyledProperty<IBinding?> IconMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
nameof(IconMemberBinding)); nameof(IconMemberBinding));
@@ -66,6 +69,29 @@ public class Timeline: ItemsControl
set => SetValue(DescriptionTemplateProperty, value); 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) protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{ {
recycleKey = null; recycleKey = null;

View File

@@ -0,0 +1,9 @@
namespace Ursa.Controls;
public enum TimelineDisplayMode
{
Left,
Center,
Right,
Alternate,
}

View File

@@ -8,9 +8,13 @@ using Avalonia.Media;
namespace Ursa.Controls; namespace Ursa.Controls;
[PseudoClasses(":first", ":last")] [PseudoClasses(PC_First, PC_Last, PC_EmptyIcon)]
public class TimelineItem: HeaderedContentControl 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?>( public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<TimelineItem, object?>(
nameof(Icon)); nameof(Icon));
@@ -37,10 +41,47 @@ public class TimelineItem: HeaderedContentControl
get => GetValue(TypeProperty); get => GetValue(TypeProperty);
set => SetValue(TypeProperty, value); 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) internal void SetEnd(bool start, bool end)
{ {
PseudoClasses.Set(":first", start); PseudoClasses.Set(PC_First, start);
PseudoClasses.Set(":last", end); PseudoClasses.Set(PC_Last, end);
} }
} }

View 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);
}
}