From 868e07ff89e83480213f5af7a96d949a69a3421e Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 4 Oct 2026 19:12:49 +0200 Subject: [PATCH] Show what a coupler connects as a stripe per connection Under the coupler's icon runs a stripe for each connection in its own colour - brake red, multiple-unit blue, high voltage yellow and so on - so the strip tells the kinds of coupling apart, not just coupled from free. The editor's checkboxes carry the same colours as the legend. The ambient coolant flag leaves the coupler flyout: it is the vehicle's and already sits in the vehicle panel, beside the crew and the wagon number. --- StarterNG/Views/Depot/CouplerLook.cs | 56 ++++++++++++++++++------- StarterNG/Views/Depot/VehicleCards.cs | 49 ++++++++++++---------- StarterNG/Views/Depot/VehicleDetails.cs | 2 +- 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/StarterNG/Views/Depot/CouplerLook.cs b/StarterNG/Views/Depot/CouplerLook.cs index b81ffcf..df9edc4 100644 --- a/StarterNG/Views/Depot/CouplerLook.cs +++ b/StarterNG/Views/Depot/CouplerLook.cs @@ -1,5 +1,8 @@ using System.Collections.Generic; using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Layout; using Avalonia.Media; using Material.Icons; using StarterNG.Classes; @@ -28,21 +31,35 @@ public enum CouplerState } /// -/// How a coupler is drawn on the consist strip, so its state reads at a glance: -/// an icon and a bar under it that ties the two cards, both in the state's colour. -/// Green is left out - on the strip it means selected or turned round. +/// How a coupler is drawn on the consist strip. Under an icon for the coupler as a +/// whole runs a bundle of stripes, one per connection in its own colour, so what +/// is coupled reads at a glance; the coupler editor shows the same colours as its +/// legend. Green is left out - on the strip it means selected or turned round. /// public static class CouplerLook { private static readonly IBrush Steel = Solid("#C3CBD3"); private static readonly IBrush Dim = Solid("#6B737B"); - private static readonly IBrush Sky = Solid("#4FB8FF"); - private static readonly IBrush Violet = Solid("#B08CFF"); private static readonly IBrush Red = Solid("#FF5A5A"); private static readonly IBrush Amber = Solid("#F0A030"); + /// One per bit of , in the same order. + private static readonly IBrush[] BitBrushes = + { + Solid("#C3CBD3"), // mechanical - steel + Solid("#FF5A5A"), // brake pipe - red + Solid("#4FB8FF"), // multiple-unit control - blue + Solid("#FFD43B"), // high voltage - yellow + Solid("#B08CFF"), // gangway - violet + Solid("#FF9F43"), // auxiliary air - orange + Solid("#FF6FB5"), // heating - pink + Solid("#7A838C") // workshop lock - dark steel + }; + private static IBrush Solid(string color) => new SolidColorBrush(Color.Parse(color)); + public static IBrush BitBrush(int index) => BitBrushes[index]; + public static CouplerState StateOf(Coupling coupling, bool trailing) { int flags = coupling.AbsFlags; @@ -67,25 +84,34 @@ public static class CouplerLook public static IBrush Brush(CouplerState state) => state switch { CouplerState.Free => Dim, - CouplerState.MultipleUnit => Sky, - CouplerState.Permanent => Violet, CouplerState.EndSignals => Red, CouplerState.NoEndSignals => Amber, _ => Steel }; - /// The bar under the icon: none at the back, broken where nothing is coupled. - public static bool HasBar(CouplerState state) => state is not (CouplerState.EndSignals or CouplerState.NoEndSignals); + /// The bits that are set, as indexes into . + public static IEnumerable SetBits(Coupling coupling) => + Enumerable.Range(0, CouplingBits.BitKeys.Length).Where(i => coupling.Has(1 << i)); - /// Thicker where the vehicles do not part in shunting. - public static double BarThickness(CouplerState state) => state == CouplerState.Permanent ? 5 : 3; + /// A connection's name with its colour beside it, for the editor's checkboxes. + public static Control BitLabel(int index) => new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 6, + Children = + { + new Border + { + Width = 10, Height = 10, CornerRadius = new CornerRadius(2), + Background = BitBrushes[index], VerticalAlignment = VerticalAlignment.Center + }, + new TextBlock { Text = App.Loc[CouplingBits.BitKeys[index]], VerticalAlignment = VerticalAlignment.Center } + } + }; /// What the coupler connects, by the names the coupler editor uses. public static IReadOnlyList Connections(Coupling coupling) => - Enumerable.Range(0, CouplingBits.BitKeys.Length) - .Where(i => coupling.Has(1 << i)) - .Select(i => App.Loc[CouplingBits.BitKeys[i]]) - .ToList(); + SetBits(coupling).Select(i => App.Loc[CouplingBits.BitKeys[i]]).ToList(); /// The tooltip: the code as the scenery has it, then what it means. public static string Describe(Coupling coupling, bool trailing) diff --git a/StarterNG/Views/Depot/VehicleCards.cs b/StarterNG/Views/Depot/VehicleCards.cs index 22a8dbd..a5ed5d3 100644 --- a/StarterNG/Views/Depot/VehicleCards.cs +++ b/StarterNG/Views/Depot/VehicleCards.cs @@ -416,12 +416,11 @@ public sealed class VehicleCards { var coupling = Consist.TailCar(item).Coupling; var icon = new MaterialIcon { Width = 18, Height = 18, HorizontalAlignment = HorizontalAlignment.Center }; - var bar = new Line + // Room for all eight, so every icon on the strip sits level. + var stripes = new StackPanel { - StartPoint = new Point(0, 0), - EndPoint = new Point(22, 0), - HorizontalAlignment = HorizontalAlignment.Center, - StrokeLineCap = PenLineCap.Round + Spacing = 1, Height = 23, + HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top }; var coupler = new Button { @@ -429,20 +428,33 @@ public sealed class VehicleCards MinWidth = 0, Cursor = _hand, VerticalAlignment = VerticalAlignment.Center, - Content = new StackPanel { Spacing = 4, Children = { icon, bar } } + Content = new StackPanel { Spacing = 4, Children = { icon, stripes } } }; coupler.Classes.Add("Basic"); void Restyle() { var state = CouplerLook.StateOf(coupling, trailing); - var brush = CouplerLook.Brush(state); icon.Kind = CouplerLook.Icon(state); - icon.Foreground = brush; - bar.Opacity = CouplerLook.HasBar(state) ? 1 : 0; // kept in the layout, so every icon sits level - bar.Stroke = brush; - bar.StrokeThickness = CouplerLook.BarThickness(state); - bar.StrokeDashArray = state == CouplerState.Free ? new AvaloniaList { 1.5, 1.5 } : null; + icon.Foreground = CouplerLook.Brush(state); + + // A stripe per connection, the way the hoses and cables hang between + // the buffers; nothing coupled leaves a broken line. + stripes.Children.Clear(); + foreach (int bit in CouplerLook.SetBits(coupling)) + stripes.Children.Add(new Border + { + Width = 22, Height = 2, CornerRadius = new CornerRadius(1), + Background = CouplerLook.BitBrush(bit) + }); + if (stripes.Children.Count == 0 && !trailing) + stripes.Children.Add(new Line + { + StartPoint = new Point(0, 0), EndPoint = new Point(22, 0), + Stroke = CouplerLook.Brush(state), StrokeThickness = 2, + StrokeDashArray = new AvaloniaList { 2, 2 } + }); + ToolTip.SetTip(coupler, CouplerLook.Describe(coupling, trailing)); } @@ -523,7 +535,7 @@ public sealed class VehicleCards int bit = 1 << i; var check = new CheckBox { - Content = App.Loc[CouplingBits.BitKeys[i]], + Content = CouplerLook.BitLabel(i), IsChecked = d.Coupling.Has(bit), FontSize = 12 }; @@ -540,15 +552,8 @@ public sealed class VehicleCards } panel.Children.Add(grid); - var thermo = new CheckBox - { - Content = App.Loc["ThermoAmbient"], - IsChecked = d.Coupling.ThermoDynamic, - FontSize = 12, - Margin = new Thickness(0, 2, 0, 0) - }; - thermo.IsCheckedChanged += (_, _) => d.Coupling.ThermoDynamic = thermo.IsChecked == true; - panel.Children.Add(thermo); + // The ambient coolant flag rides in the coupling token too, but it is the + // vehicle's: it sits in the vehicle panel, beside the crew and the number. return new Border { Padding = new Thickness(8), Child = panel }; } diff --git a/StarterNG/Views/Depot/VehicleDetails.cs b/StarterNG/Views/Depot/VehicleDetails.cs index a64c3d8..c170b7a 100644 --- a/StarterNG/Views/Depot/VehicleDetails.cs +++ b/StarterNG/Views/Depot/VehicleDetails.cs @@ -196,7 +196,7 @@ public sealed class VehicleDetails int bit = 1 << i; var check = new CheckBox { - Content = App.Loc[CouplingBits.BitKeys[i]], + Content = CouplerLook.BitLabel(i), IsChecked = d.Coupling.Has(bit), Classes = { "Checklist" } };