Files
ClaudeDo/src/ClaudeDo.Ui/Converters/SimpleConverters.cs
T

90 lines
3.9 KiB
C#

using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using ClaudeDo.Ui.ViewModels.Islands;
namespace ClaudeDo.Ui.Converters;
// One-expression converters. Each is only worth a class because Avalonia XAML can't
// 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>
/// 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 ColorBrushConverter Instance { get; } = new();
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> 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>
public sealed class EqualsParameterConverter : OneWayConverter
{
public static EqualsParameterConverter Instance { get; } = new();
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> Equals(value?.ToString(), parameter as string);
}
public sealed class UpperCaseConverter : OneWayConverter
{
public static UpperCaseConverter Instance { get; } = new();
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value?.ToString()?.ToUpperInvariant();
}
public sealed class StrikeIfTrueConverter : OneWayConverter
{
public static StrikeIfTrueConverter Instance { get; } = new();
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is true ? TextDecorations.Strikethrough : null;
}
public sealed class BoolToItalicConverter : OneWayConverter
{
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is true ? FontStyle.Italic : FontStyle.Normal;
}
public sealed class BoolToDraftOpacityConverter : OneWayConverter
{
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is true ? 0.7 : 1.0;
}
// Drives ListBoxItem.Focusable for TasksIslandView's flat Rows list: a HeaderRow container must
// not be a tab stop, a TaskRowViewModel container must behave like any other row.
public sealed class NotHeaderRowConverter : OneWayConverter
{
public static NotHeaderRowConverter Instance { get; } = new();
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is not HeaderRow;
}
public abstract class OneWayConverter : IValueConverter
{
public abstract object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture);
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}