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. /// /// A stored list/folder colour to a brush: "#RRGGBB" becomes a , a swatch key /// ("Swatch3") resolves to the app resource "Swatch3Brush"; null, empty or unknown falls back to the resource named by the parameter. /// 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; /// Resolves a stored colour against (app resources in production); split out so tests need no Avalonia app. internal static object? Resolve(string? value, string? fallbackKey, Func 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); } } /// True when the value's string form equals the parameter (segmented-switch active state). 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(); }