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" } };