feat: add color preview control.

This commit is contained in:
rabbitism 2023-02-13 00:14:56 +08:00
parent 888d115ca8
commit bf87bfeb90
6 changed files with 274 additions and 71 deletions

View File

@ -0,0 +1,109 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Semi.Avalonia.Demo.Controls">
<PathGeometry x:Key="CopyIcon">M5 7C3.89543 7 3 7.89543 3 9V19C3 20.1046 3.89543 21 5 21H15C16.1046 21 17 20.1046 17 19V9C17 7.89543 16.1046 7 15 7H5Z,M7 4C7 2.89543 7.89543 2 9 2H20C21.1046 2 22 2.89543 22 4V15C22 16.1046 21.1046 17 20 17H19V8C19 6 18 5 16 5H7V4Z</PathGeometry>
<ControlTheme x:Key="{x:Type controls:ColorDetailControl}" TargetType="controls:ColorDetailControl">
<!-- Add Resources Here -->
<Setter Property="controls:ColorDetailControl.Template">
<ControlTemplate TargetType="controls:ColorDetailControl">
<StackPanel>
<TextBlock
Margin="0,0,0,8"
Classes="H5"
Text="{TemplateBinding ResourceName}"
Theme="{DynamicResource TitleTextBlock}" />
<Border
Height="100"
HorizontalAlignment="Stretch"
Background="{TemplateBinding Background}"
CornerRadius="6" />
<Grid ColumnDefinitions="*, Auto" RowDefinitions="*, *, *, *, *, *">
<!-- Row 0-1 ResourceKey -->
<TextBlock
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4,8,0,0"
VerticalAlignment="Center"
Classes="Tertiary"
Text="ResourceKey" />
<SelectableTextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Text="{TemplateBinding ResourceKey}" />
<Button
Grid.Row="1"
Grid.Column="1"
Classes="Tertiary"
Command="{Binding $parent[controls:ColorDetailControl].Copy}"
CommandParameter="{x:Static controls:ColorDetailControl.KEY_ResourceKey}"
Theme="{DynamicResource BorderlessButton}">
<PathIcon
Width="12"
Height="12"
Data="{StaticResource CopyIcon}"
Foreground="{Binding $parent[Button].Foreground}" />
</Button>
<!-- Row 2-3 HEX -->
<TextBlock
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4,8,0,0"
VerticalAlignment="Center"
Classes="Tertiary"
Text="ARGB" />
<SelectableTextBlock
Grid.Row="3"
Grid.Column="0"
VerticalAlignment="Center"
Text="{TemplateBinding Hex}" />
<Button
Grid.Row="3"
Grid.Column="1"
Classes="Tertiary"
Command="{Binding $parent[controls:ColorDetailControl].Copy}"
CommandParameter="{x:Static controls:ColorDetailControl.KEY_Hex}"
Theme="{DynamicResource BorderlessButton}">
<PathIcon
Width="12"
Height="12"
Data="{StaticResource CopyIcon}"
Foreground="{Binding $parent[Button].Foreground}" />
</Button>
<!-- Row 4-5 Opacity -->
<TextBlock
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4,8,0,0"
VerticalAlignment="Center"
Classes="Tertiary"
Text="Opacity" />
<SelectableTextBlock
Grid.Row="5"
Grid.Column="0"
VerticalAlignment="Center"
Text="{TemplateBinding OpacityNumber}" />
<Button
Grid.Row="5"
Grid.Column="1"
Classes="Tertiary"
Command="{Binding $parent[controls:ColorDetailControl].Copy}"
CommandParameter="{x:Static controls:ColorDetailControl.KEY_Opacity}"
Theme="{DynamicResource BorderlessButton}">
<PathIcon
Width="12"
Height="12"
Data="{StaticResource CopyIcon}"
Foreground="{Binding $parent[Button].Foreground}" />
</Button>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@ -0,0 +1,91 @@
using System.Globalization;
using Avalonia;
using Avalonia.Controls.Primitives;
using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace Semi.Avalonia.Demo.Controls;
public class ColorDetailControl: TemplatedControl
{
public const string KEY_ResourceKey = "ResourceKey";
public const string KEY_Hex = "Hex";
public const string KEY_Opacity = "Opacity";
public static readonly StyledProperty<string?> ResourceKeyProperty = AvaloniaProperty.Register<ColorDetailControl, string?>(
nameof(ResourceKey));
public string? ResourceKey
{
get => GetValue(ResourceKeyProperty);
set => SetValue(ResourceKeyProperty, value);
}
public static readonly StyledProperty<string?> ResourceNameProperty = AvaloniaProperty.Register<ColorDetailControl, string?>(
nameof(ResourceName));
public string? ResourceName
{
get => GetValue(ResourceNameProperty);
set => SetValue(ResourceNameProperty, value);
}
public static readonly DirectProperty<ColorDetailControl, string?> HexProperty = AvaloniaProperty.RegisterDirect<ColorDetailControl, string?>(
nameof(Hex), o => o.Hex);
private string? _hex;
public string? Hex
{
get => _hex;
private set => SetAndRaise(HexProperty, ref _hex, value);
}
public static readonly DirectProperty<ColorDetailControl, string?> OpacityNumberProperty = AvaloniaProperty.RegisterDirect<ColorDetailControl, string?>(
nameof(OpacityNumber), o => o.OpacityNumber);
private string? _opacityNumber;
public string? OpacityNumber
{
get => _opacityNumber;
private set => SetAndRaise(OpacityNumberProperty, ref _opacityNumber, value);
}
static ColorDetailControl()
{
BackgroundProperty.Changed.AddClassHandler<ColorDetailControl>((o, e) => o.OnBackgroundChanged(e));
}
private void OnBackgroundChanged(AvaloniaPropertyChangedEventArgs args)
{
var color = args.GetNewValue<IBrush>();
if (color is ISolidColorBrush b)
{
Hex = b.Color.ToString().ToUpperInvariant();
OpacityNumber = b.Opacity.ToString(CultureInfo.InvariantCulture);
}
}
public async void Copy(object o)
{
string? text = null;
if (o is string s)
{
switch (s)
{
case KEY_ResourceKey: text = ResourceKey;
break;
case KEY_Hex: text = Hex;
break;
case KEY_Opacity: text = OpacityNumber;
break;
default: text = string.Empty; break;
}
}
if (Application.Current is { Clipboard: { } c })
{
await c.SetTextAsync(text??string.Empty);
}
}
}

View File

@ -27,7 +27,7 @@
Name="PART_HexTextBlock" Name="PART_HexTextBlock"
Padding="8" Padding="8"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
FontSize="8" FontSize="10"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
IsVisible="False" IsVisible="False"
Opacity="0.8" Opacity="0.8"

View File

@ -16,6 +16,7 @@
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="../Controls/ColorItemControl.axaml" /> <ResourceInclude Source="../Controls/ColorItemControl.axaml" />
<ResourceInclude Source="../Controls/ColorDetailControl.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
@ -31,6 +32,7 @@
<ToggleButton <ToggleButton
Name="toggle" Name="toggle"
HorizontalAlignment="Right" HorizontalAlignment="Right"
IsChecked="True"
Theme="{DynamicResource BorderlessToggleButton}"> Theme="{DynamicResource BorderlessToggleButton}">
<PathIcon <PathIcon
Width="16" Width="16"
@ -38,39 +40,34 @@
Data="M5 2H19C20.6569 2 22 3.34315 22 5V19C22 20.6569 20.6569 22 19 22H5C3.34315 22 2 20.6569 2 19V5C2 3.34315 3.34315 2 5 2ZM6 4C5.44772 4 5 4.44772 5 5V19C5 19.5523 5.44772 20 6 20H9C9.55229 20 10 19.5523 10 19V5C10 4.44772 9.55229 4 9 4H6Z" /> Data="M5 2H19C20.6569 2 22 3.34315 22 5V19C22 20.6569 20.6569 22 19 22H5C3.34315 22 2 20.6569 2 19V5C2 3.34315 3.34315 2 5 2ZM6 4C5.44772 4 5 4.44772 5 5V19C5 19.5523 5.44772 20 6 20H9C9.55229 20 10 19.5523 10 19V5C10 4.44772 9.55229 4 9 4H6Z" />
</ToggleButton> </ToggleButton>
<Border IsVisible="{Binding #splitView.IsPaneOpen}" Theme="{DynamicResource CardBorder}"> <Border IsVisible="{Binding #splitView.IsPaneOpen}" Theme="{DynamicResource CardBorder}">
<StackPanel DataContext="{Binding SelectedColor}" Spacing="8"> <controls:ColorDetailControl
<Border Background="{Binding SelectedColor.Brush}"
Height="30" IsVisible="{Binding SelectedColor, Converter={x:Static ObjectConverters.IsNotNull}}"
HorizontalAlignment="Stretch" ResourceKey="{Binding SelectedColor.ResourceKey}"
Background="{Binding Color}" ResourceName="{Binding SelectedColor.ColorDisplayName}" />
CornerRadius="3" />
<Label Classes="Code" Content="{Binding ResourceKey}" />
<TextBlock Text="{Binding Color}" />
</StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>
</SplitView.Pane> </SplitView.Pane>
<SplitView.Content> <SplitView.Content>
<ScrollViewer> <ScrollViewer>
<StackPanel> <StackPanel>
<TabControl> <TabControl>
<TabItem Header="Light"> <TabItem Header="Light">
<ItemsControl Margin="16" Items="{Binding LightSeries}"> <ItemsControl Margin="16" Items="{Binding LightLists}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" /> <WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:ColorSeries"> <DataTemplate x:DataType="viewModels:ColorListViewModel">
<ItemsControl Margin="4,0" Items="{Binding Color}"> <ItemsControl Margin="4,0" Items="{Binding Color}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:ColorItemViewModel"> <DataTemplate x:DataType="viewModels:ColorItemViewModel">
<controls:ColorItemControl <controls:ColorItemControl
Background="{Binding Color}" Background="{Binding Brush}"
ColorName="{Binding Name}" ColorName="{Binding ColorDisplayName}"
Foreground="{Binding TextColor}" Foreground="{Binding TextBrush}"
Hex="{Binding Hex}" /> Hex="{Binding Hex}" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
@ -80,21 +77,21 @@
</ItemsControl> </ItemsControl>
</TabItem> </TabItem>
<TabItem Header="Dark"> <TabItem Header="Dark">
<ItemsControl Margin="16" Items="{Binding DarkSeries}"> <ItemsControl Margin="16" Items="{Binding DarkLists}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" /> <WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:ColorSeries"> <DataTemplate x:DataType="viewModels:ColorListViewModel">
<ItemsControl Margin="4,0" Items="{Binding Color}"> <ItemsControl Margin="4,0" Items="{Binding Color}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:ColorItemViewModel"> <DataTemplate x:DataType="viewModels:ColorItemViewModel">
<controls:ColorItemControl <controls:ColorItemControl
Background="{Binding Color}" Background="{Binding Brush}"
ColorName="{Binding Name}" ColorName="{Binding ColorDisplayName}"
Foreground="{Binding TextColor}" Foreground="{Binding TextBrush}"
Hex="{Binding Hex}" /> Hex="{Binding Hex}" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@ -10,9 +10,10 @@ namespace Semi.Avalonia.Demo.ViewModels;
public class PaletteDemoViewModel: ObservableObject public class PaletteDemoViewModel: ObservableObject
{ {
private string[] _colors = { "Amber","Blue","Cyan","Green","Grey","Indigo","LightBlue","LightGreen","Lime","Orange","Pink","Purple","Red","Teal","Violet","Yellow" }; private readonly string[] _predefinedColorNames = { "Amber","Blue","Cyan","Green","Grey","Indigo","LightBlue","LightGreen","Lime","Orange","Pink","Purple","Red","Teal","Violet","Yellow" };
private ObservableCollection<ColorSeries> _lightSeries; private IResourceDictionary _lightResourceDictionary;
private IResourceDictionary _darkResourceDictionary;
private ColorItemViewModel _selectedColor; private ColorItemViewModel _selectedColor;
public ColorItemViewModel SelectedColor public ColorItemViewModel SelectedColor
@ -20,49 +21,54 @@ public class PaletteDemoViewModel: ObservableObject
get => _selectedColor; get => _selectedColor;
set => SetProperty(ref _selectedColor, value); set => SetProperty(ref _selectedColor, value);
} }
public ObservableCollection<ColorSeries> LightSeries
{
get => _lightSeries;
set => SetProperty(ref _lightSeries, value);
}
private ObservableCollection<ColorSeries> _darkSeries;
private ObservableCollection<ColorListViewModel> _lightLists;
public ObservableCollection<ColorSeries> DarkSeries public ObservableCollection<ColorListViewModel> LightLists
{ {
get => _darkSeries; get => _lightLists;
set => SetProperty(ref _darkSeries, value); set => SetProperty(ref _lightLists, value);
}
private ObservableCollection<ColorListViewModel> _darkLists;
public ObservableCollection<ColorListViewModel> DarkLists
{
get => _darkLists;
set => SetProperty(ref _darkLists, value);
} }
public PaletteDemoViewModel() public PaletteDemoViewModel()
{ {
LightSeries = new ObservableCollection<ColorSeries>(); _lightResourceDictionary = (ResourceDictionary)AvaloniaXamlLoader.Load(new Uri("avares://Semi.Avalonia/Themes/Light/Palette.axaml"));
var lightResourceDictionary = (ResourceDictionary)(AvaloniaXamlLoader.Load(new Uri("avares://Semi.Avalonia/Themes/Light/Palette.axaml"))); _darkResourceDictionary = (ResourceDictionary)AvaloniaXamlLoader.Load(new Uri("avares://Semi.Avalonia/Themes/Dark/Palette.axaml"));
foreach (var color in _colors) InitializePalette();
{
ColorSeries s = new ColorSeries();
s.Initialize(lightResourceDictionary, color, true);
LightSeries.Add(s);
}
DarkSeries = new ObservableCollection<ColorSeries>();
var darkResouceDictionary = (ResourceDictionary)(AvaloniaXamlLoader.Load(new Uri("avares://Semi.Avalonia/Themes/Dark/Palette.axaml")));
foreach (var color in _colors)
{
ColorSeries s = new ColorSeries();
s.Initialize(darkResouceDictionary, color, false);
DarkSeries.Add(s);
}
WeakReferenceMessenger.Default.Register<PaletteDemoViewModel, ColorItemViewModel>(this, OnClickColorItem); WeakReferenceMessenger.Default.Register<PaletteDemoViewModel, ColorItemViewModel>(this, OnClickColorItem);
} }
private void InitializePalette()
{
LightLists = new ObservableCollection<ColorListViewModel>();
foreach (var color in _predefinedColorNames)
{
ColorListViewModel s = new ColorListViewModel();
s.Initialize(_lightResourceDictionary, color, true);
LightLists.Add(s);
}
DarkLists = new ObservableCollection<ColorListViewModel>();
foreach (var color in _predefinedColorNames)
{
ColorListViewModel s = new ColorListViewModel();
s.Initialize(_darkResourceDictionary, color, false);
DarkLists.Add(s);
}
}
private void OnClickColorItem(PaletteDemoViewModel vm, ColorItemViewModel item) private void OnClickColorItem(PaletteDemoViewModel vm, ColorItemViewModel item)
{ {
SelectedColor = item; SelectedColor = item;
} }
} }
public class ColorSeries: ObservableObject public class ColorListViewModel: ObservableObject
{ {
private ObservableCollection<ColorItemViewModel>? _colors; private ObservableCollection<ColorItemViewModel>? _colors;
@ -104,25 +110,25 @@ public class ColorSeries: ObservableObject
public class ColorItemViewModel : ObservableObject public class ColorItemViewModel : ObservableObject
{ {
private IBrush _color; private IBrush _brush;
public IBrush Color public IBrush Brush
{ {
get => _color; get => _brush;
set => SetProperty(ref _color, value); set => SetProperty(ref _brush, value);
} }
private IBrush _textColor; private IBrush _textBrush;
public IBrush TextColor public IBrush TextBrush
{ {
get => _textColor; get => _textBrush;
set => SetProperty(ref _textColor, value); set => SetProperty(ref _textBrush, value);
} }
private string _name; private string _colorDisplayName;
public string Name public string ColorDisplayName
{ {
get => _name; get => _colorDisplayName;
set => SetProperty(ref _name, value); set => SetProperty(ref _colorDisplayName, value);
} }
private string _resourceKey; private string _resourceKey;
@ -141,19 +147,19 @@ public class ColorItemViewModel : ObservableObject
set => SetProperty(ref _hex, value); set => SetProperty(ref _hex, value);
} }
public ColorItemViewModel(string name, IBrush color, string resourceKey, bool light, int index) public ColorItemViewModel(string colorDisplayName, IBrush brush, string resourceKey, bool light, int index)
{ {
Name = name; ColorDisplayName = colorDisplayName;
Color = color; Brush = brush;
ResourceKey = resourceKey; ResourceKey = resourceKey;
Hex = color.ToString().ToUpperInvariant(); Hex = brush.ToString().ToUpperInvariant();
if ((light && index < 5) || (!light && index > 5)) if ((light && index < 5) || (!light && index > 5))
{ {
TextColor = Brushes.Black; TextBrush = Brushes.Black;
} }
else else
{ {
TextColor = Brushes.White; TextBrush = Brushes.White;
} }
} }
} }

View File

@ -9,5 +9,5 @@
<sys:TimeSpan x:Key="SplitViewPaneAnimationCloseDuration">00:00:00.1</sys:TimeSpan> <sys:TimeSpan x:Key="SplitViewPaneAnimationCloseDuration">00:00:00.1</sys:TimeSpan>
<Easing x:Key="SplitViewPaneAnimationEasing">0.1,0.9,0.2,1.0</Easing> <Easing x:Key="SplitViewPaneAnimationEasing">0.1,0.9,0.2,1.0</Easing>
<SolidColorBrush x:Key="SplitViewSeparatorBackground" Opacity="0.08" Color="#F9F9F9" /> <SolidColorBrush x:Key="SplitViewSeparatorBackground" Opacity="0.08" Color="#F9F9F9" />
<SolidColorBrush x:Key="SplitViewPaneBackground" Color="Black" /> <SolidColorBrush x:Key="SplitViewPaneBackground" Color="#16161A" />
</ResourceDictionary> </ResourceDictionary>