Fluent light/dark theme dictionaries (accent/surface/card/border/danger), OS-following variant, App-level text/button/card styles. Per-protocol vector icons via ProtocolIdToIconConverter (mirrors StatusKindToBrushConverter, view-layer, safe fallback, no external assets). Curated layout across dashboard/detail/config: consistent spacing scale, card grouping, theme brushes, accent/danger buttons. App-only; no VM contract change, no new NuGet, no fw bump. Projects: Junction.App (+ Junction.Tests: +10 converter tests, 148 pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
3.3 KiB
C#
75 lines
3.3 KiB
C#
using System;
|
||
using System.Globalization;
|
||
using Avalonia.Data.Converters;
|
||
using Avalonia.Media;
|
||
|
||
namespace Junction.App.Converters
|
||
{
|
||
/// <summary>
|
||
/// Maps a machine's <c>ProtocolId</c> ("mtconnect", future "opcua"/"fanuc", …) to a vector
|
||
/// glyph (<see cref="Geometry"/>) for a <c>PathIcon</c>. Keeps view-models framework-agnostic:
|
||
/// the icon decision lives entirely in the view layer, mirroring
|
||
/// <see cref="StatusKindToBrushConverter"/>. Unknown/empty ids fall back to a generic glyph.
|
||
/// <para>Glyphs are line-only 24×24 path data (no arcs/curves) so they always parse and read
|
||
/// as distinct filled shapes. No external image assets — net48 packaging stays a single exe set.</para>
|
||
/// </summary>
|
||
public sealed class ProtocolIdToIconConverter : IValueConverter
|
||
{
|
||
public static readonly ProtocolIdToIconConverter Instance = new ProtocolIdToIconConverter();
|
||
|
||
// Signal-strength bars — a live telemetry feed.
|
||
private const string MtconnectData = "M4,14 H7 V20 H4 Z M10,10 H13 V20 H10 Z M16,5 H19 V20 H16 Z";
|
||
|
||
// Stacked diamonds — the OPC UA layered address space.
|
||
private const string OpcuaData = "M12,2 L20,7 L12,12 L4,7 Z M12,13 L20,18 L12,23 L4,18 Z";
|
||
|
||
// Plus/cross — an industrial CNC controller marker.
|
||
private const string FanucData = "M10,3 H14 V10 H21 V14 H14 V21 H10 V14 H3 V10 H10 Z";
|
||
|
||
// Octagon node — generic / unknown protocol.
|
||
private const string FallbackData = "M8,4 H16 L20,8 V16 L16,20 H8 L4,16 V8 Z";
|
||
|
||
private static Geometry? _mtconnect;
|
||
private static Geometry? _opcua;
|
||
private static Geometry? _fanuc;
|
||
private static Geometry? _fallback;
|
||
|
||
/// <summary>
|
||
/// Pure lookup of the raw path-data string for a protocol id. View-framework-free and
|
||
/// side-effect-free so it is unit-testable without an Avalonia platform. Known ids return a
|
||
/// distinct non-empty string; anything else returns the generic fallback. Never throws.
|
||
/// </summary>
|
||
public static string PathDataFor(string? protocolId)
|
||
{
|
||
switch (protocolId == null ? null : protocolId.Trim().ToLowerInvariant())
|
||
{
|
||
case "mtconnect":
|
||
return MtconnectData;
|
||
case "opcua":
|
||
return OpcuaData;
|
||
case "fanuc":
|
||
return FanucData;
|
||
default:
|
||
return FallbackData;
|
||
}
|
||
}
|
||
|
||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||
{
|
||
switch (PathDataFor(value as string))
|
||
{
|
||
case MtconnectData:
|
||
return _mtconnect ?? (_mtconnect = Geometry.Parse(MtconnectData));
|
||
case OpcuaData:
|
||
return _opcua ?? (_opcua = Geometry.Parse(OpcuaData));
|
||
case FanucData:
|
||
return _fanuc ?? (_fanuc = Geometry.Parse(FanucData));
|
||
default:
|
||
return _fallback ?? (_fallback = Geometry.Parse(FallbackData));
|
||
}
|
||
}
|
||
|
||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||
=> throw new NotSupportedException();
|
||
}
|
||
}
|