mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-16 23:49:19 +02:00
Colour the depot couplers by what they connect
Each coupler on the consist strip now shows its state: free, coupled, multiple-unit, permanent, and at the back whether the end signals go up. The tooltip gives the code and the connections instead of the drag hint, and the flyout edits mark the coupler as set by hand and restyle it in place. The note about 0 on the last vehicle is gone - the colour says it.
This commit is contained in:
25
StarterNG.Tests/CouplerLookTests.cs
Normal file
25
StarterNG.Tests/CouplerLookTests.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Views;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class CouplerLookTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("0", CouplerState.Free)]
|
||||
[InlineData("3", CouplerState.Coupled)]
|
||||
[InlineData("7", CouplerState.MultipleUnit)]
|
||||
[InlineData("131", CouplerState.Permanent)]
|
||||
[InlineData("-3", CouplerState.Permanent)]
|
||||
[InlineData("0.BP", CouplerState.Free)]
|
||||
public void A_coupler_between_vehicles_reads_by_what_it_connects(string code, CouplerState expected) =>
|
||||
Assert.Equal(expected, CouplerLook.StateOf(Coupling.Parse(code), trailing: false));
|
||||
|
||||
[Theory]
|
||||
[InlineData("0", CouplerState.EndSignals)]
|
||||
[InlineData("0.BP", CouplerState.EndSignals)]
|
||||
[InlineData("3", CouplerState.CoupledBehind)]
|
||||
[InlineData("-7", CouplerState.CoupledBehind)]
|
||||
public void The_back_of_the_train_gets_end_signals_only_at_0(string code, CouplerState expected) =>
|
||||
Assert.Equal(expected, CouplerLook.StateOf(Coupling.Parse(code), trailing: true));
|
||||
}
|
||||
95
StarterNG/Views/Depot/CouplerLook.cs
Normal file
95
StarterNG/Views/Depot/CouplerLook.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia.Media;
|
||||
using StarterNG.Classes;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
public enum CouplerState
|
||||
{
|
||||
/// <summary>Nothing connected between the two vehicles.</summary>
|
||||
Free,
|
||||
|
||||
/// <summary>Coupled the ordinary way: mechanically, the brake pipe and so on.</summary>
|
||||
Coupled,
|
||||
|
||||
/// <summary>Coupled with multiple-unit control.</summary>
|
||||
MultipleUnit,
|
||||
|
||||
/// <summary>Not to be parted in shunting: workshop lock, or a locked coupler.</summary>
|
||||
Permanent,
|
||||
|
||||
/// <summary>The back of the train at 0 - the simulator puts up the end signals.</summary>
|
||||
EndSignals,
|
||||
|
||||
/// <summary>The back of the train coupled to whatever stands behind it - no end signals.</summary>
|
||||
CoupledBehind
|
||||
}
|
||||
|
||||
/// <summary>How a coupler is drawn on the consist strip, so its state reads at a glance.</summary>
|
||||
public static class CouplerLook
|
||||
{
|
||||
private static readonly IBrush Dim = new SolidColorBrush(Color.Parse("#808080"));
|
||||
private static readonly IBrush Green = new SolidColorBrush(Color.Parse("#41C400"));
|
||||
private static readonly IBrush Blue = new SolidColorBrush(Color.Parse("#4AA3FF"));
|
||||
private static readonly IBrush Red = new SolidColorBrush(Color.Parse("#E5484D"));
|
||||
private static readonly IBrush Amber = new SolidColorBrush(Color.Parse("#E0A030"));
|
||||
|
||||
public static CouplerState StateOf(Coupling coupling, bool trailing)
|
||||
{
|
||||
int flags = coupling.AbsFlags;
|
||||
if (trailing)
|
||||
return flags == 0 ? CouplerState.EndSignals : CouplerState.CoupledBehind;
|
||||
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;
|
||||
}
|
||||
|
||||
// Box drawing and a plain disc: Windows draws no colour emoji for these.
|
||||
public static string Glyph(CouplerState state) => state switch
|
||||
{
|
||||
CouplerState.Free => "╎",
|
||||
CouplerState.Permanent => "═",
|
||||
CouplerState.EndSignals => "●",
|
||||
_ => "≣"
|
||||
};
|
||||
|
||||
/// <summary>The colour of the glyph; null keeps the ordinary text colour.</summary>
|
||||
public static IBrush? Brush(CouplerState state) => state switch
|
||||
{
|
||||
CouplerState.Free => Dim,
|
||||
CouplerState.MultipleUnit => Green,
|
||||
CouplerState.Permanent => Blue,
|
||||
CouplerState.EndSignals => Red,
|
||||
CouplerState.CoupledBehind => Amber,
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>What the coupler connects, by the names the coupler editor uses.</summary>
|
||||
public static IReadOnlyList<string> Connections(Coupling coupling) =>
|
||||
Enumerable.Range(0, CouplingBits.BitKeys.Length)
|
||||
.Where(i => coupling.Has(1 << i))
|
||||
.Select(i => App.Loc[CouplingBits.BitKeys[i]])
|
||||
.ToList();
|
||||
|
||||
/// <summary>The tooltip: the code as the scenery has it, then what it means.</summary>
|
||||
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.CoupledBehind => string.Format(App.Loc["CouplerCoupledBehind"], connections),
|
||||
_ => string.Format(App.Loc["CouplerConnects"], connections)
|
||||
};
|
||||
|
||||
var lines = new List<string> { 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);
|
||||
}
|
||||
}
|
||||
@@ -406,13 +406,16 @@ public sealed class VehicleCards
|
||||
flyout.ShowAt(anchor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The coupler after a unit, drawn by its state - free, coupled, multiple-unit,
|
||||
/// permanent, and at the back of the train whether the end signals go up.
|
||||
/// </summary>
|
||||
public Control BuildCoupler(ConsistItem item, bool trailing = false)
|
||||
{
|
||||
var coupling = Consist.TailCar(item).Coupling;
|
||||
var glyph = new TextBlock
|
||||
{
|
||||
Text = trailing ? "╡" : "≣",
|
||||
FontSize = 18,
|
||||
Opacity = trailing ? 0.55 : 0.7,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
@@ -422,24 +425,55 @@ public sealed class VehicleCards
|
||||
MinWidth = 0,
|
||||
Cursor = _hand,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Content = glyph,
|
||||
Flyout = new Flyout
|
||||
{
|
||||
Content = BuildCouplingBox(item),
|
||||
Placement = PlacementMode.Top
|
||||
}
|
||||
Content = glyph
|
||||
};
|
||||
coupler.Classes.Add("Basic");
|
||||
ToolTip.SetTip(coupler, $"{App.Loc["Coupling"]} — {App.Loc["TipReorder"]}");
|
||||
|
||||
void Restyle()
|
||||
{
|
||||
var state = CouplerLook.StateOf(coupling, trailing);
|
||||
glyph.Text = CouplerLook.Glyph(state);
|
||||
if (CouplerLook.Brush(state) is { } brush)
|
||||
{
|
||||
glyph.Foreground = brush;
|
||||
glyph.Opacity = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
glyph.ClearValue(TextBlock.ForegroundProperty);
|
||||
glyph.Opacity = 0.7;
|
||||
}
|
||||
ToolTip.SetTip(coupler, CouplerLook.Describe(coupling, trailing));
|
||||
}
|
||||
|
||||
Restyle();
|
||||
coupler.Flyout = new Flyout
|
||||
{
|
||||
Content = BuildCouplingBox(item, Restyle),
|
||||
Placement = PlacementMode.Top
|
||||
};
|
||||
return coupler;
|
||||
}
|
||||
|
||||
private Control BuildCouplingBox(ConsistItem item)
|
||||
/// <summary>
|
||||
/// Edits the coupler in place, so the flyout stays open while bits are
|
||||
/// ticked; <paramref name="changed"/> redraws the coupler it hangs off.
|
||||
/// </summary>
|
||||
private Control BuildCouplingBox(ConsistItem item, Action changed)
|
||||
{
|
||||
var d = Consist.TailCar(item);
|
||||
int unitIdx = _consist.IndexOf(item);
|
||||
|
||||
var panel = new StackPanel { Spacing = 2 };
|
||||
var checks = new List<(CheckBox Check, int Bit)>();
|
||||
bool syncing = false;
|
||||
|
||||
void Changed()
|
||||
{
|
||||
d.CouplerSetByHand = true;
|
||||
changed();
|
||||
_refreshDetails();
|
||||
}
|
||||
|
||||
var copy = new Button
|
||||
{
|
||||
@@ -456,7 +490,11 @@ public sealed class VehicleCards
|
||||
{
|
||||
if (unitIdx <= 0) return;
|
||||
d.Coupling.Flags = Consist.TailCar(_consist[unitIdx - 1]).Coupling.Flags;
|
||||
_redraw();
|
||||
syncing = true;
|
||||
foreach (var (check, bit) in checks)
|
||||
check.IsChecked = d.Coupling.Has(bit);
|
||||
syncing = false;
|
||||
Changed();
|
||||
};
|
||||
|
||||
var header = new DockPanel { Margin = new Thickness(0, 0, 0, 2) };
|
||||
@@ -489,7 +527,13 @@ public sealed class VehicleCards
|
||||
IsChecked = d.Coupling.Has(bit),
|
||||
FontSize = 12
|
||||
};
|
||||
check.IsCheckedChanged += (_, _) => d.Coupling.Set(bit, check.IsChecked == true);
|
||||
check.IsCheckedChanged += (_, _) =>
|
||||
{
|
||||
if (syncing) return;
|
||||
d.Coupling.Set(bit, check.IsChecked == true);
|
||||
Changed();
|
||||
};
|
||||
checks.Add((check, bit));
|
||||
Grid.SetRow(check, i / 2);
|
||||
Grid.SetColumn(check, i % 2);
|
||||
grid.Children.Add(check);
|
||||
|
||||
@@ -178,10 +178,6 @@ public sealed class VehicleDetails
|
||||
if (unitIdx < 0)
|
||||
return;
|
||||
|
||||
bool isLast = unitIdx >= _consist.Count - 1;
|
||||
if (isLast)
|
||||
couplerPanel.Children.Add(PanelNote(App.Loc["CouplingLastNote"]));
|
||||
|
||||
var d = Consist.TailCar(item);
|
||||
|
||||
// Two columns: the tab is wide enough and the list is short enough
|
||||
|
||||
@@ -284,10 +284,15 @@
|
||||
<String key="TipSplit">Rozdělit jednotku na jednotlivé vozy</String>
|
||||
<String key="AppliesToUnit">Společné pro celou jednotku</String>
|
||||
<String key="CouplingUnitRear">Spřáhlo na konci jednotky</String>
|
||||
<String key="CouplingLastNote">Poslední vozidlo: spřáhlo 0 dává koncovky, jiný kód spojí soupravu s tím, co stojí za ní, bez koncovek. Starter zde po každé změně soupravy nastaví 0, pokud spřáhlo nenastavíte ručně; „Auto“ vždy vrátí 0.</String>
|
||||
<String key="Join">Spojit</String>
|
||||
<String key="TipJoin">Spojit toto vozidlo s následujícím do jedné jednotky</String>
|
||||
<String key="TipReorder">Přetáhněte pro změnu pořadí</String>
|
||||
<String key="CouplerCode">Kód spřáhla: {0}</String>
|
||||
<String key="CouplerFree">Vozidla nejsou spojena.</String>
|
||||
<String key="CouplerConnects">Spojeno: {0}</String>
|
||||
<String key="CouplerEndSignals">Konec soupravy — simulátor vyvěsí koncovky.</String>
|
||||
<String key="CouplerCoupledBehind">Konec soupravy se spřáhne s tím, co stojí za ní ({0}) — bez koncovek.</String>
|
||||
<String key="CouplerLocked">Zablokováno — při posunu se nerozpojí.</String>
|
||||
<String key="CouplerClickToEdit">Klikněte pro změnu.</String>
|
||||
<String key="AddVehicle">Přidat</String>
|
||||
<String key="AddMany">Přidat vícekrát…</String>
|
||||
<String key="AddManyCount">Kolikrát vozidlo přidat</String>
|
||||
|
||||
@@ -284,10 +284,15 @@
|
||||
<String key="TipSplit">Split the unit into individual cars</String>
|
||||
<String key="AppliesToUnit">Shared by the whole unit</String>
|
||||
<String key="CouplingUnitRear">Coupler at the rear of the unit</String>
|
||||
<String key="CouplingLastNote">Last vehicle: coupler 0 gets the end signals; any other code couples the train to whatever stands behind it, with no end signals. The starter sets 0 here after every change to the consist, unless you set the coupler by hand; “Auto” always brings 0 back.</String>
|
||||
<String key="Join">Join</String>
|
||||
<String key="TipJoin">Couple this vehicle with the next one into a single unit</String>
|
||||
<String key="TipReorder">Drag to reorder</String>
|
||||
<String key="CouplerCode">Coupler code: {0}</String>
|
||||
<String key="CouplerFree">The vehicles are not coupled.</String>
|
||||
<String key="CouplerConnects">Connected: {0}</String>
|
||||
<String key="CouplerEndSignals">End of the train — the simulator puts up the end signals.</String>
|
||||
<String key="CouplerCoupledBehind">The end of the train couples to whatever stands behind it ({0}) — no end signals.</String>
|
||||
<String key="CouplerLocked">Locked — not uncoupled in shunting.</String>
|
||||
<String key="CouplerClickToEdit">Click to change.</String>
|
||||
<String key="AddVehicle">Add</String>
|
||||
<String key="AddMany">Add several…</String>
|
||||
<String key="AddManyCount">How many times to add it</String>
|
||||
|
||||
@@ -284,10 +284,15 @@
|
||||
<String key="TipSplit">Az egység szétbontása külön kocsikra</String>
|
||||
<String key="AppliesToUnit">Az egész egységre közös</String>
|
||||
<String key="CouplingUnitRear">Az egység végén lévő kapcsolókészülék</String>
|
||||
<String key="CouplingLastNote">Utolsó jármű: a 0-s kapcsolat adja a zárjelzőket, más kód a mögötte állóhoz kapcsolja a szerelvényt, zárjelzők nélkül. A starter minden szerelvényváltozás után 0-ra állítja, hacsak kézzel nem állítod be; az „Auto” mindig visszaállítja a 0-t.</String>
|
||||
<String key="Join">Összekapcsolás</String>
|
||||
<String key="TipJoin">A jármű összekapcsolása a következővel egy egységgé</String>
|
||||
<String key="TipReorder">Húzza az átrendezéshez</String>
|
||||
<String key="CouplerCode">Kapcsolat kódja: {0}</String>
|
||||
<String key="CouplerFree">A járművek nincsenek összekapcsolva.</String>
|
||||
<String key="CouplerConnects">Összekapcsolva: {0}</String>
|
||||
<String key="CouplerEndSignals">A szerelvény vége — a szimulátor kiteszi a zárjelzőket.</String>
|
||||
<String key="CouplerCoupledBehind">A szerelvény vége összekapcsolódik a mögötte állóval ({0}) — zárjelzők nélkül.</String>
|
||||
<String key="CouplerLocked">Zárolva — tolatáskor nem bontható.</String>
|
||||
<String key="CouplerClickToEdit">Kattints a módosításhoz.</String>
|
||||
<String key="AddVehicle">Hozzáadás</String>
|
||||
<String key="AddMany">Hozzáadás többször…</String>
|
||||
<String key="AddManyCount">Hányszor add hozzá a járművet</String>
|
||||
|
||||
@@ -284,10 +284,15 @@
|
||||
<String key="TipSplit">Rozdziel zespół na pojedyncze człony</String>
|
||||
<String key="AppliesToUnit">Ustawienie wspólne dla całego zespołu</String>
|
||||
<String key="CouplingUnitRear">Sprzęg na końcu zespołu</String>
|
||||
<String key="CouplingLastNote">Ostatni pojazd: sprzęg 0 daje końcówki, a inny kod połączy skład z tym, co stoi za nim, bez końcówek. Starter ustawia tu 0 po każdej zmianie składu, chyba że ustawisz sprzęg ręcznie; „Auto” zawsze przywraca 0.</String>
|
||||
<String key="Join">Złącz</String>
|
||||
<String key="TipJoin">Połącz ten pojazd z następnym w jeden zespół</String>
|
||||
<String key="TipReorder">Przeciągnij, aby zmienić kolejność</String>
|
||||
<String key="CouplerCode">Kod sprzęgu: {0}</String>
|
||||
<String key="CouplerFree">Pojazdy nie są połączone.</String>
|
||||
<String key="CouplerConnects">Połączone: {0}</String>
|
||||
<String key="CouplerEndSignals">Koniec składu — symulator wystawi końcówki.</String>
|
||||
<String key="CouplerCoupledBehind">Koniec składu sprzęgnie się z tym, co stoi za nim ({0}) — bez końcówek.</String>
|
||||
<String key="CouplerLocked">Zablokowany — nie rozprzęga się przy manewrach.</String>
|
||||
<String key="CouplerClickToEdit">Kliknij, aby zmienić.</String>
|
||||
<String key="AddVehicle">Dodaj</String>
|
||||
<String key="AddMany">Dodaj wiele…</String>
|
||||
<String key="AddManyCount">Ile razy dodać pojazd</String>
|
||||
|
||||
@@ -284,10 +284,15 @@
|
||||
<String key="TipSplit">Разделить сцеп на отдельные вагоны</String>
|
||||
<String key="AppliesToUnit">Общее для всего состава</String>
|
||||
<String key="CouplingUnitRear">Сцепка в конце состава</String>
|
||||
<String key="CouplingLastNote">Последняя единица: сцепка 0 даёт хвостовые сигналы, другой код сцепит состав с тем, что стоит за ним, без хвостовых сигналов. Стартер ставит здесь 0 после каждого изменения состава, если вы не задали сцепку вручную; «Авто» всегда возвращает 0.</String>
|
||||
<String key="Join">Соединить</String>
|
||||
<String key="TipJoin">Соединить этот вагон со следующим в один состав</String>
|
||||
<String key="TipReorder">Перетащите для изменения порядка</String>
|
||||
<String key="CouplerCode">Код сцепки: {0}</String>
|
||||
<String key="CouplerFree">Вагоны не сцеплены.</String>
|
||||
<String key="CouplerConnects">Соединено: {0}</String>
|
||||
<String key="CouplerEndSignals">Хвост поезда — симулятор вывесит хвостовые сигналы.</String>
|
||||
<String key="CouplerCoupledBehind">Хвост поезда сцепится с тем, что стоит за ним ({0}) — без хвостовых сигналов.</String>
|
||||
<String key="CouplerLocked">Заблокирована — не расцепляется при маневрах.</String>
|
||||
<String key="CouplerClickToEdit">Нажмите, чтобы изменить.</String>
|
||||
<String key="AddVehicle">Добавить</String>
|
||||
<String key="AddMany">Добавить несколько…</String>
|
||||
<String key="AddManyCount">Сколько раз добавить единицу</String>
|
||||
|
||||
Reference in New Issue
Block a user