feat(claude-do): merge Selectable colors for list dots and folders: accent-aware pa
ClaudeDo-Task: 98844480cc1f4e748908d2ce8ac6c50b
This commit is contained in:
@@ -5,20 +5,30 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace ClaudeDo.Ui;
|
||||
|
||||
public sealed record AccentPreset(string Name, string Accent, string Dim, string Soft, string Glow);
|
||||
/// <summary>An accent theme; <paramref name="Swatches"/> holds one hex per <c>SwatchNBrush</c> token (index 0 = Swatch1).</summary>
|
||||
public sealed record AccentPreset(string Name, string Accent, string Dim, string Soft, string Glow, string[] Swatches);
|
||||
|
||||
public static class AccentPresets
|
||||
{
|
||||
// Hue 88 — moss/sage green (original)
|
||||
public static readonly AccentPreset Moss = new("moss", "#FF7C9166", "#FF64785A", "#FF3E4B39", "#387C9166");
|
||||
public static readonly AccentPreset Moss = new("moss", "#FF7C9166", "#FF64785A", "#FF3E4B39", "#387C9166",
|
||||
["#7C9166", "#D4A574", "#C87060", "#D9C27A", "#6FB3A8", "#7FA7C9", "#A38BC9", "#C77FA0", "#8B9D7A", "#B58B6A", "#5C78B8", "#9AA0A6"]);
|
||||
// Hue ~40 — warm earthy brown/terra
|
||||
public static readonly AccentPreset Peat = new("peat", "#FF9A7B5C", "#FF7F6449", "#FF4D3C2C", "#389A7B5C");
|
||||
public static readonly AccentPreset Peat = new("peat", "#FF9A7B5C", "#FF7F6449", "#FF4D3C2C", "#389A7B5C",
|
||||
["#9A7B5C", "#D9A066", "#C4604F", "#DBBF6A", "#7FA890", "#7C9AB8", "#A98BB8", "#C98A8A", "#A39A6E", "#B8825A", "#6E80AE", "#A09A90"]);
|
||||
// Hue ~180 — cool teal/sea-green
|
||||
public static readonly AccentPreset Sea = new("sea", "#FF5B8F8C", "#FF4A7573", "#FF263D3C", "#385B8F8C");
|
||||
public static readonly AccentPreset Sea = new("sea", "#FF5B8F8C", "#FF4A7573", "#FF263D3C", "#385B8F8C",
|
||||
["#5B8F8C", "#C9A87C", "#BF7468", "#CBC27F", "#4FA396", "#5FA8C8", "#8E8BCC", "#B87FA5", "#7C9A8E", "#A8907A", "#5C78B8", "#8FA0A8"]);
|
||||
|
||||
public static readonly IReadOnlyList<AccentPreset> All = [Moss, Peat, Sea];
|
||||
public static AccentPreset Default => Moss;
|
||||
|
||||
/// <summary>Number of <c>SwatchNBrush</c> tokens every preset must define.</summary>
|
||||
public const int SwatchCount = 12;
|
||||
|
||||
/// <summary>Swatch keys ("Swatch1".."SwatchN") as stored on lists and folders.</summary>
|
||||
public static readonly string[] SwatchKeys = Enumerable.Range(1, SwatchCount).Select(i => $"Swatch{i}").ToArray();
|
||||
|
||||
public static AccentPreset Find(string? name) =>
|
||||
All.FirstOrDefault(p => p.Name == name) ?? Default;
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ new editor boilerplate there rather than copying it a third time.
|
||||
|
||||
## Converters
|
||||
|
||||
In `Converters/` — grep rather than list: log-level brush, dot brush,
|
||||
In `Converters/` — `ColorBrushConverter` (in `SimpleConverters.cs`) is the one resolver for stored list/folder colours (swatch key → `SwatchNBrush`, `#RRGGBB` → brush, else the `ConverterParameter` resource); the `Swatch1-12Brush` tokens are rewritten in place per accent preset by `AccentPresetService`, so every `AccentPreset` must carry all `AccentPresets.SwatchCount` hexes. Otherwise grep rather than list: log-level brush, dot brush,
|
||||
status equality, icon key, `KeepLastNumber`. The one-expression ones
|
||||
(strike/italic/opacity toggles, uppercase, not-a-header-row) share
|
||||
`SimpleConverters.cs`; put new trivial ones there instead of a new file.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="12.0.4" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="12.0.4" />
|
||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="12.0.0" />
|
||||
<PackageReference Include="Material.Icons" Version="3.0.2" />
|
||||
<PackageReference Include="TextMateSharp.Grammars" Version="2.0.3" />
|
||||
|
||||
@@ -9,14 +9,32 @@ namespace ClaudeDo.Ui.Converters;
|
||||
// instantiate a generic FuncValueConverter as a resource; they stay one per type so the
|
||||
// x:Key names in App.axaml keep resolving. Anything with real logic gets its own file.
|
||||
|
||||
/// <summary>"#RRGGBB" to a brush; anything else falls back to the app resource named by the parameter.</summary>
|
||||
public sealed class HexBrushConverter : OneWayConverter
|
||||
/// <summary>
|
||||
/// A stored list/folder colour to a brush: "#RRGGBB" becomes a <see cref="SolidColorBrush"/>, a swatch key
|
||||
/// ("Swatch3") resolves to the app resource "Swatch3Brush"; null, empty or unknown falls back to the resource named by the parameter.
|
||||
/// </summary>
|
||||
public sealed class ColorBrushConverter : OneWayConverter
|
||||
{
|
||||
public static HexBrushConverter Instance { get; } = new();
|
||||
public static ColorBrushConverter Instance { get; } = new();
|
||||
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is string s && Color.TryParse(s, out var c)
|
||||
? new SolidColorBrush(c)
|
||||
: Avalonia.Application.Current?.TryGetResource(parameter as string ?? "", null, out var res) == true ? res : null;
|
||||
=> Resolve(value as string, parameter as string, LookupResource);
|
||||
|
||||
private static object? LookupResource(string key)
|
||||
=> Avalonia.Application.Current?.TryGetResource(key, null, out var res) == true ? res : null;
|
||||
|
||||
/// <summary>Resolves a stored colour against <paramref name="lookup"/> (app resources in production); split out so tests need no Avalonia app.</summary>
|
||||
internal static object? Resolve(string? value, string? fallbackKey, Func<string, object?> lookup)
|
||||
{
|
||||
if (value is { Length: > 0 })
|
||||
{
|
||||
if (value[0] == '#')
|
||||
{
|
||||
if (Color.TryParse(value, out var c)) return new SolidColorBrush(c);
|
||||
}
|
||||
else if (lookup(value + "Brush") is { } swatch) return swatch;
|
||||
}
|
||||
return fallbackKey is null ? null : lookup(fallbackKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>True when the value's string form equals the parameter (segmented-switch active state).</summary>
|
||||
|
||||
@@ -114,6 +114,21 @@
|
||||
<SolidColorBrush x:Key="Identity5Brush" Color="#C77FA0" />
|
||||
<SolidColorBrush x:Key="Identity6Brush" Color="#5C78B8" />
|
||||
|
||||
<!-- Default colour swatches for lists and folders. Hexes are the Moss preset's; AccentPresetService
|
||||
rewrites them in place on a preset switch, so everything bound to a swatch re-tints live. -->
|
||||
<SolidColorBrush x:Key="Swatch1Brush" Color="#7C9166" />
|
||||
<SolidColorBrush x:Key="Swatch2Brush" Color="#D4A574" />
|
||||
<SolidColorBrush x:Key="Swatch3Brush" Color="#C87060" />
|
||||
<SolidColorBrush x:Key="Swatch4Brush" Color="#D9C27A" />
|
||||
<SolidColorBrush x:Key="Swatch5Brush" Color="#6FB3A8" />
|
||||
<SolidColorBrush x:Key="Swatch6Brush" Color="#7FA7C9" />
|
||||
<SolidColorBrush x:Key="Swatch7Brush" Color="#A38BC9" />
|
||||
<SolidColorBrush x:Key="Swatch8Brush" Color="#C77FA0" />
|
||||
<SolidColorBrush x:Key="Swatch9Brush" Color="#8B9D7A" />
|
||||
<SolidColorBrush x:Key="Swatch10Brush" Color="#B58B6A" />
|
||||
<SolidColorBrush x:Key="Swatch11Brush" Color="#5C78B8" />
|
||||
<SolidColorBrush x:Key="Swatch12Brush" Color="#9AA0A6" />
|
||||
|
||||
<!-- Merge editor (3-pane conflict resolver) block tints -->
|
||||
<SolidColorBrush x:Key="MergeOursTintBrush" Color="#1F7C9166" /> <!-- ours side (moss) -->
|
||||
<SolidColorBrush x:Key="MergeTheirsTintBrush" Color="#1FD4A574" /> <!-- theirs side (amber) -->
|
||||
|
||||
@@ -14,6 +14,8 @@ public static class AccentPresetService
|
||||
SetBrushColor(app, "AccentSoftBrush", preset.Soft);
|
||||
SetBrushColor(app, "AccentGlowBrush", preset.Glow);
|
||||
SetBrushColor(app, "MossBrush", preset.Accent);
|
||||
for (var i = 0; i < preset.Swatches.Length; i++)
|
||||
SetBrushColor(app, $"Swatch{i + 1}Brush", preset.Swatches[i]);
|
||||
}
|
||||
|
||||
private static void SetBrushColor(Application app, string key, string hex)
|
||||
|
||||
@@ -38,8 +38,16 @@ public sealed partial class ListNavItemViewModel : ViewModelBase
|
||||
/// <summary>No icon picked — the row shows a coloured dot instead of a glyph.</summary>
|
||||
public bool IsDot => IconKey is null or ListsIslandViewModel.ListIcon;
|
||||
|
||||
/// <summary>Picked colour (swatch key or "#RRGGBB"); null = the hash-derived identity colour.</summary>
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(DotColor))]
|
||||
private string? _color;
|
||||
|
||||
/// <summary>Identity colour of the dot, stable per list id across restarts.</summary>
|
||||
public string DotColorKey => $"Identity{ConPtyPaneViewModel.StableIndex(Id, 6) + 1}";
|
||||
|
||||
/// <summary>What the dot renders: the picked colour, else <see cref="DotColorKey"/>.</summary>
|
||||
public string DotColor => string.IsNullOrEmpty(Color) ? DotColorKey : Color;
|
||||
// The sidebar folder this row sits in (ListEntity.FolderId); null = top level.
|
||||
[ObservableProperty] private string? _folderId;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
var rawId = row.Id.StartsWith("user:", StringComparison.Ordinal) ? row.Id["user:".Length..] : row.Id;
|
||||
var vm = _services.GetRequiredService<ListSettingsModalViewModel>();
|
||||
vm.ErrorReported += msg => ErrorReported?.Invoke(msg);
|
||||
vm.BeginLoad(rawId, row.Name, row.WorkingDir, row.DefaultCommitType, row.IsManual, row.FindingsTracked, row.IconKey);
|
||||
vm.BeginLoad(rawId, row.Name, row.WorkingDir, row.DefaultCommitType, row.IsManual, row.FindingsTracked, row.IconKey, color: row.Color);
|
||||
await Dialogs.ShowListSettingsAsync(vm);
|
||||
if (vm.Deleted) await LoadAsync();
|
||||
else await RefreshRowAsync(row.Id);
|
||||
@@ -373,6 +373,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
Name = l.Name,
|
||||
Kind = ListKind.User,
|
||||
IconKey = l.Icon ?? ListIcon,
|
||||
Color = l.Color,
|
||||
WorkingDir = l.WorkingDir,
|
||||
DefaultCommitType = l.DefaultCommitType,
|
||||
IsManual = l.IsManual,
|
||||
@@ -492,7 +493,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
var vm = _services.GetRequiredService<ListSettingsModalViewModel>();
|
||||
vm.ErrorReported += msg => ErrorReported?.Invoke(msg);
|
||||
// canToggleFolder: only the create flow offers "make this a folder instead".
|
||||
vm.BeginLoad(entity.Id, entity.Name, entity.WorkingDir, entity.DefaultCommitType, entity.IsManual, entity.FindingsTracked, entity.Icon, canToggleFolder: true);
|
||||
vm.BeginLoad(entity.Id, entity.Name, entity.WorkingDir, entity.DefaultCommitType, entity.IsManual, entity.FindingsTracked, entity.Icon, canToggleFolder: true, color: entity.Color);
|
||||
await Dialogs.ShowListSettingsAsync(vm);
|
||||
if (!vm.Saved && !vm.Deleted)
|
||||
{
|
||||
@@ -845,6 +846,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
row.IsManual = entity.IsManual;
|
||||
row.FindingsTracked = entity.FindingsTracked;
|
||||
row.IconKey = entity.Icon ?? ListIcon;
|
||||
row.Color = entity.Color;
|
||||
row.HasTicketProject = hasTicketProject;
|
||||
RecomputeHasNoLinkedRepo();
|
||||
}
|
||||
@@ -891,6 +893,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
|
||||
Name = entity.Name,
|
||||
Kind = ListKind.User,
|
||||
IconKey = entity.Icon ?? ListIcon,
|
||||
Color = entity.Color,
|
||||
WorkingDir = entity.WorkingDir,
|
||||
DefaultCommitType = entity.DefaultCommitType,
|
||||
IsManual = entity.IsManual,
|
||||
|
||||
@@ -115,12 +115,42 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
|
||||
public const string DefaultIcon = "Dot";
|
||||
|
||||
/// <summary>Folder colour swatches; "" = no colour (theme default).</summary>
|
||||
public static readonly string[] FolderColorOptions =
|
||||
["", "#D4A574", "#C87060", "#D9C27A", "#8B9D7A", "#6FB3A8", "#7FA7C9", "#A38BC9"];
|
||||
/// <summary>Picker swatches: "" (default) followed by the accent-aware palette keys.</summary>
|
||||
public static readonly string[] SwatchOptions = ["", .. AccentPresets.SwatchKeys];
|
||||
|
||||
/// <summary>Selected folder colour, "" for none.</summary>
|
||||
[ObservableProperty] private string _folderColor = "";
|
||||
/// <summary>Stored colour: "" (default), a swatch key, or a literal "#RRGGBB". Shared by lists and folders.</summary>
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomColor))]
|
||||
private string _selectedColor = "";
|
||||
|
||||
/// <summary>True when <see cref="SelectedColor"/> is a custom hex rather than a swatch.</summary>
|
||||
public bool IsCustomColor => SelectedColor.StartsWith('#');
|
||||
|
||||
// Separate from SelectedColor: the ListBox pushes null when a custom hex (not in SwatchOptions) is current.
|
||||
[ObservableProperty] private string? _selectedSwatch = "";
|
||||
|
||||
/// <summary>Bound to the custom ColorPicker; writes back as "#RRGGBB".</summary>
|
||||
[ObservableProperty] private Avalonia.Media.Color _customColor = Avalonia.Media.Color.Parse(AccentPresets.Default.Accent);
|
||||
|
||||
private bool _syncingColor;
|
||||
|
||||
partial void OnSelectedColorChanged(string value)
|
||||
{
|
||||
_syncingColor = true;
|
||||
SelectedSwatch = SwatchOptions.Contains(value) ? value : null;
|
||||
if (IsCustomColor && Avalonia.Media.Color.TryParse(value, out var c)) CustomColor = c;
|
||||
_syncingColor = false;
|
||||
}
|
||||
|
||||
partial void OnSelectedSwatchChanged(string? value)
|
||||
{
|
||||
if (!_syncingColor && value is not null) SelectedColor = value;
|
||||
}
|
||||
|
||||
partial void OnCustomColorChanged(Avalonia.Media.Color value)
|
||||
{
|
||||
if (!_syncingColor) SelectedColor = $"#{value.R:X2}{value.G:X2}{value.B:X2}";
|
||||
}
|
||||
|
||||
/// <summary>Default icon for folders, kept apart from <see cref="DefaultIcon"/> so lists never look like folders.</summary>
|
||||
public const string DefaultFolderIcon = "Folder";
|
||||
@@ -226,10 +256,11 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
bool isManual = false,
|
||||
bool findingsTracked = false,
|
||||
string? icon = null,
|
||||
bool canToggleFolder = false)
|
||||
bool canToggleFolder = false,
|
||||
string? color = null)
|
||||
{
|
||||
CanToggleFolder = canToggleFolder;
|
||||
_ = LoadAsync(listId, name, workingDir, defaultCommitType, isManual, findingsTracked, icon);
|
||||
_ = LoadAsync(listId, name, workingDir, defaultCommitType, isManual, findingsTracked, icon, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -238,7 +269,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
public void BeginLoadFolder(string folderId, string name, string? icon, string? color = null)
|
||||
{
|
||||
FolderColor = color ?? "";
|
||||
SelectedColor = color ?? "";
|
||||
_folderId = folderId;
|
||||
IsFolder = true;
|
||||
CanToggleFolder = false;
|
||||
@@ -254,6 +285,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
bool isManual = false,
|
||||
bool findingsTracked = false,
|
||||
string? icon = null,
|
||||
string? color = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
ListConfigDto? cfg = null;
|
||||
@@ -268,6 +300,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
// An unknown/legacy key would leave the picker with no selection and silently reset the
|
||||
// icon on the next save — fall back to the default instead.
|
||||
Icon = IsKnownIcon(icon) ? icon! : DefaultIcon;
|
||||
SelectedColor = color ?? "";
|
||||
WorkingDir = workingDir ?? "";
|
||||
DefaultCommitType = string.IsNullOrWhiteSpace(defaultCommitType) ? CommitTypeRegistry.DefaultType : defaultCommitType;
|
||||
|
||||
@@ -364,7 +397,8 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
DefaultCommitType,
|
||||
IsManual,
|
||||
FindingsTracked,
|
||||
Icon));
|
||||
Icon,
|
||||
NullIfEmpty(SelectedColor)));
|
||||
|
||||
// Tri-state: dropdown never shown (no base URL), or ticket projects failed/never loaded
|
||||
// (e.g. an unreachable ticket system) -> null, leave the stored link alone. Dropdown shown
|
||||
@@ -398,7 +432,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Name = name,
|
||||
Icon = Icon,
|
||||
Color = NullIfEmpty(FolderColor),
|
||||
Color = NullIfEmpty(SelectedColor),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
});
|
||||
// The create flow pre-creates a list row for this modal to save against; a folder
|
||||
@@ -409,7 +443,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
}
|
||||
else
|
||||
{
|
||||
await lists.RenameFolderAsync(_folderId, name, Icon, NullIfEmpty(FolderColor));
|
||||
await lists.RenameFolderAsync(_folderId, name, Icon, NullIfEmpty(SelectedColor));
|
||||
}
|
||||
|
||||
Saved = true;
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
Data="{Binding IconKey, Converter={StaticResource IconKey}}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Ellipse Width="8" Height="8" IsVisible="{Binding IsDot}"
|
||||
Fill="{Binding DotColorKey, Converter={StaticResource DotBrush}}"
|
||||
Fill="{Binding DotColor, Converter={x:Static converters:ColorBrushConverter.Instance}, ConverterParameter=MossBrush}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Panel>
|
||||
<!-- Name -->
|
||||
@@ -137,24 +137,24 @@
|
||||
</ContextMenu>
|
||||
</Border.ContextMenu>
|
||||
<Grid ColumnDefinitions="12,20,*,Auto">
|
||||
<PathIcon Grid.Column="0" Classes="list-icon" Foreground="{Binding Color, Converter={x:Static converters:HexBrushConverter.Instance}, ConverterParameter=TextMuteBrush}"
|
||||
<PathIcon Grid.Column="0" Classes="list-icon" Foreground="{Binding Color, Converter={x:Static converters:ColorBrushConverter.Instance}, ConverterParameter=TextMuteBrush}"
|
||||
Width="9" Height="9"
|
||||
Data="{StaticResource Icon.ChevronRight}"
|
||||
IsVisible="{Binding IsCollapsed}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<PathIcon Grid.Column="0" Classes="list-icon" Foreground="{Binding Color, Converter={x:Static converters:HexBrushConverter.Instance}, ConverterParameter=TextMuteBrush}"
|
||||
<PathIcon Grid.Column="0" Classes="list-icon" Foreground="{Binding Color, Converter={x:Static converters:ColorBrushConverter.Instance}, ConverterParameter=TextMuteBrush}"
|
||||
Width="9" Height="9"
|
||||
Data="{StaticResource Icon.ChevronDown}"
|
||||
IsVisible="{Binding !IsCollapsed}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<PathIcon Grid.Column="1" Classes="list-icon" Foreground="{Binding Color, Converter={x:Static converters:HexBrushConverter.Instance}, ConverterParameter=TextMuteBrush}"
|
||||
<PathIcon Grid.Column="1" Classes="list-icon" Foreground="{Binding Color, Converter={x:Static converters:ColorBrushConverter.Instance}, ConverterParameter=TextMuteBrush}"
|
||||
Width="14" Height="14"
|
||||
Data="{Binding IconKey, Converter={StaticResource IconKey}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" Classes="list-label"
|
||||
Text="{Binding Name}" ToolTip.Tip="{Binding Name}"
|
||||
VerticalAlignment="Center" Margin="8,0" FontWeight="SemiBold"
|
||||
Foreground="{Binding Color, Converter={x:Static converters:HexBrushConverter.Instance}, ConverterParameter=TextDimBrush}"
|
||||
Foreground="{Binding Color, Converter={x:Static converters:ColorBrushConverter.Instance}, ConverterParameter=TextDimBrush}"
|
||||
FontSize="{StaticResource FontSizeBody}"/>
|
||||
<TextBlock Grid.Column="3" Classes="list-count"
|
||||
Text="{Binding Count}" IsVisible="{Binding ShowCount}"/>
|
||||
@@ -166,7 +166,7 @@
|
||||
<ctl:ExpandPanel IsExpanded="{Binding !IsCollapsed}">
|
||||
<Panel>
|
||||
<Border Width="1" HorizontalAlignment="Left" Margin="8,0,0,4"
|
||||
Background="{Binding Color, Converter={x:Static converters:HexBrushConverter.Instance}, ConverterParameter=LineBrush}"
|
||||
Background="{Binding Color, Converter={x:Static converters:ColorBrushConverter.Instance}, ConverterParameter=LineBrush}"
|
||||
IsHitTestVisible="False"/>
|
||||
<ItemsControl ItemsSource="{Binding Children}" Margin="14,0,0,0"/>
|
||||
</Panel>
|
||||
|
||||
@@ -89,12 +89,28 @@
|
||||
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="4" IsVisible="{Binding IsFolder}">
|
||||
<TextBlock Classes="field-label" Text="{loc:Tr modals.listSettings.folderColor}"/>
|
||||
<ListBox ItemsSource="{x:Static vm:ListSettingsModalViewModel.FolderColorOptions}"
|
||||
SelectedItem="{Binding FolderColor, Mode=TwoWay}"
|
||||
<!-- Colour: swatches follow the accent preset (brushes mutated in place), the picker stores a literal hex.
|
||||
The empty ring is "default". Shared by lists (dot) and folders (name + guide line). -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="field-label" Text="{loc:Tr modals.listSettings.color}"/>
|
||||
<ListBox ItemsSource="{x:Static vm:ListSettingsModalViewModel.SwatchOptions}"
|
||||
SelectedItem="{Binding SelectedSwatch, Mode=TwoWay}"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
HorizontalAlignment="Left">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="MinHeight" Value="0"/>
|
||||
</Style>
|
||||
<Style Selector="Border.swatch">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource LineBrightBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected Border.swatch">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource TextBrush}"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel/>
|
||||
@@ -102,14 +118,27 @@
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="x:String">
|
||||
<!-- "" renders as an empty ring: the "no colour" swatch. -->
|
||||
<Border Width="18" Height="18" Margin="3" CornerRadius="9"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource LineBrightBrush}"
|
||||
Background="{Binding Converter={x:Static conv:HexBrushConverter.Instance}, ConverterParameter=Surface2Brush}"/>
|
||||
<Border Classes="swatch" Width="20" Height="20" CornerRadius="10"
|
||||
Background="{Binding Converter={x:Static conv:ColorBrushConverter.Instance}, ConverterParameter=Surface2Brush}"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<TextBlock Text="{loc:Tr modals.listSettings.folderColorHint}"
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,4,0,0">
|
||||
<TextBlock Text="{loc:Tr modals.listSettings.colorCustom}" VerticalAlignment="Center"/>
|
||||
<Border Classes="custom-swatch" Classes.on="{Binding IsCustomColor}" CornerRadius="4" Padding="2">
|
||||
<Border.Styles>
|
||||
<Style Selector="Border.custom-swatch">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style Selector="Border.custom-swatch.on">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource TextBrush}"/>
|
||||
</Style>
|
||||
</Border.Styles>
|
||||
<ColorPicker Color="{Binding CustomColor, Mode=TwoWay}" IsAlphaEnabled="False" IsAlphaVisible="False"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{loc:Tr modals.listSettings.colorHint}"
|
||||
Opacity="0.6" FontSize="12" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user