using System.Collections.Generic; using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; using Material.Icons; using StarterNG.Classes; namespace StarterNG.Views; public enum CouplerState { /// Nothing connected between the two vehicles. Free, /// Coupled the ordinary way: mechanically, the brake pipe and so on. Coupled, /// Coupled with multiple-unit control. MultipleUnit, /// Not to be parted in shunting: workshop lock, or a locked coupler. Permanent, /// The back of the train at 0 - the simulator puts up the end signals. EndSignals, /// The back of the train at anything but 0 - no end signals, and nothing else. NoEndSignals } /// /// How a coupler is drawn on the consist strip. Under an icon for the coupler as a /// whole sits a block of squares, one place and colour per connection, so what is /// coupled reads at a glance; the coupler editor shows the same colours as its /// legend. /// public static class CouplerLook { private static readonly IBrush Steel = Solid("#C3CBD3"); private static readonly IBrush Dim = Solid("#6B737B"); 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("#000000"), // mechanical - black, outlined to show on the dark strip Solid("#FF3B3B"), // brake pipe - red Solid("#3D9BFF"), // multiple-unit control - blue Solid("#FF8C1A"), // high voltage - orange Solid("#1FD67A"), // gangway - green, as an exit sign Solid("#FFD60A"), // auxiliary air - yellow Solid("#F2F2F2"), // heating - white, as steam Solid("#9AA3AC") // workshop lock - grey }; private static readonly IBrush Outline = Solid("#C3CBD3"); private static readonly IBrush EmptySlot = Solid("#4A525A"); private static IBrush Solid(string color) => new SolidColorBrush(Color.Parse(color)); public static CouplerState StateOf(Coupling coupling, bool trailing) { int flags = coupling.AbsFlags; if (trailing) return flags == 0 ? CouplerState.EndSignals : CouplerState.NoEndSignals; if (flags == 0) return CouplerState.Free; if (coupling.Locked || (flags & Coupling.WorkshopLock) != 0) return CouplerState.Permanent; return (flags & Coupling.ControlMU) != 0 ? CouplerState.MultipleUnit : CouplerState.Coupled; } public static MaterialIconKind Icon(CouplerState state) => state switch { CouplerState.Free => MaterialIconKind.LinkVariantOff, CouplerState.Permanent => MaterialIconKind.LinkLock, CouplerState.EndSignals => MaterialIconKind.RecordCircle, CouplerState.NoEndSignals => MaterialIconKind.AlertCircleOutline, _ => MaterialIconKind.LinkVariant }; public static IBrush Brush(CouplerState state) => state switch { CouplerState.Free => Dim, CouplerState.EndSignals => Red, CouplerState.NoEndSignals => Amber, _ => Steel }; /// 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)); /// /// The connections as a 2 by 4 block of squares laid out like the coupler /// editor's checkboxes, so a square's place says what it is as much as its /// colour; an empty place is a dot. /// public static void FillSlots(Grid slots, Coupling coupling) { slots.Children.Clear(); for (int i = 0; i < CouplingBits.BitKeys.Length; i++) { Control slot = coupling.Has(1 << i) ? Swatch(i, 10) : new Border { Width = 4, Height = 4, CornerRadius = new CornerRadius(2), Background = EmptySlot, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; Grid.SetRow(slot, i / 2); Grid.SetColumn(slot, i % 2); slots.Children.Add(slot); } } public static Grid Slots() => new() { ColumnDefinitions = new ColumnDefinitions("12,12"), RowDefinitions = new RowDefinitions("12,12,12,12"), HorizontalAlignment = HorizontalAlignment.Center }; /// 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 = { Swatch(index, 10), new TextBlock { Text = App.Loc[CouplingBits.BitKeys[index]], VerticalAlignment = VerticalAlignment.Center } } }; private static Border Swatch(int index, double size) => new() { Width = size, Height = size, CornerRadius = new CornerRadius(1.5), Background = BitBrushes[index], // Black needs an edge to show on the dark strip; the rest have one in their own colour. BorderBrush = index == 0 ? Outline : BitBrushes[index], BorderThickness = new Thickness(1), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; /// What the coupler connects, by the names the coupler editor uses. public static IReadOnlyList Connections(Coupling coupling) => 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) { string connections = string.Join(", ", Connections(coupling)); string meaning = StateOf(coupling, trailing) switch { CouplerState.Free => App.Loc["CouplerFree"], CouplerState.EndSignals => App.Loc["CouplerEndSignals"], CouplerState.NoEndSignals => App.Loc["CouplerNoEndSignals"], _ => string.Format(App.Loc["CouplerConnects"], connections) }; var lines = new List { string.Format(App.Loc["CouplerCode"], coupling.Flags), meaning }; if (coupling.Locked && coupling.AbsFlags != 0) lines.Add(App.Loc["CouplerLocked"]); lines.Add(App.Loc["CouplerClickToEdit"]); return string.Join("\n", lines); } }