From f4abdc0039da5dfac9cdc097dca290ec207a49e3 Mon Sep 17 00:00:00 2001 From: dtrentin Date: Wed, 22 Jul 2026 07:23:47 +0200 Subject: [PATCH] feat: themed + iconed UI polish (v0.3.1) 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) --- src/Junction.App/App.axaml | 102 +++++++++++++++- .../Converters/ProtocolIdToIconConverter.cs | 75 ++++++++++++ src/Junction.App/MainWindow.axaml | 3 +- src/Junction.App/Views/DashboardView.axaml | 110 ++++++++++-------- .../Views/MachineConfigView.axaml | 84 +++++++------ .../Views/MachineDetailView.axaml | 91 +++++++-------- tests/Junction.Tests/Junction.Tests.csproj | 2 + .../Unit/ProtocolIdToIconConverterTests.cs | 78 +++++++++++++ 8 files changed, 403 insertions(+), 142 deletions(-) create mode 100644 src/Junction.App/Converters/ProtocolIdToIconConverter.cs create mode 100644 tests/Junction.Tests/Unit/ProtocolIdToIconConverterTests.cs diff --git a/src/Junction.App/App.axaml b/src/Junction.App/App.axaml index 433d2b2..297511a 100644 --- a/src/Junction.App/App.axaml +++ b/src/Junction.App/App.axaml @@ -2,15 +2,113 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Junction.App" xmlns:conv="clr-namespace:Junction.App.Converters" - x:Class="Junction.App.App"> + x:Class="Junction.App.App" + RequestedThemeVariant="Default"> + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Junction.App/Converters/ProtocolIdToIconConverter.cs b/src/Junction.App/Converters/ProtocolIdToIconConverter.cs new file mode 100644 index 0000000..5eb7758 --- /dev/null +++ b/src/Junction.App/Converters/ProtocolIdToIconConverter.cs @@ -0,0 +1,75 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace Junction.App.Converters +{ + /// + /// Maps a machine's ProtocolId ("mtconnect", future "opcua"/"fanuc", …) to a vector + /// glyph () for a PathIcon. Keeps view-models framework-agnostic: + /// the icon decision lives entirely in the view layer, mirroring + /// . Unknown/empty ids fall back to a generic glyph. + /// 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. + /// + 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; + + /// + /// 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. + /// + 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(); + } +} diff --git a/src/Junction.App/MainWindow.axaml b/src/Junction.App/MainWindow.axaml index e2299d6..000f366 100644 --- a/src/Junction.App/MainWindow.axaml +++ b/src/Junction.App/MainWindow.axaml @@ -5,7 +5,8 @@ x:DataType="vm:MainWindowViewModel" x:CompileBindings="True" Title="Junction" - Width="800" Height="600"> + Width="900" Height="640" + Background="{DynamicResource AppBackgroundBrush}"> diff --git a/src/Junction.App/Views/DashboardView.axaml b/src/Junction.App/Views/DashboardView.axaml index f2705cb..4f92f7d 100644 --- a/src/Junction.App/Views/DashboardView.axaml +++ b/src/Junction.App/Views/DashboardView.axaml @@ -8,66 +8,80 @@ x:DataType="vm:DashboardViewModel" x:CompileBindings="True"> - - + +