mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-16 23:49:19 +02:00
WIP: settings sections, shortcuts window, depot tweaks
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -8,7 +8,7 @@
|
||||
{
|
||||
"name": "C#: Launch Startup Project",
|
||||
"type": "dotnet",
|
||||
"request": "launch"
|
||||
"request": "launch",
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
<FluentTheme />
|
||||
<materialIcons:MaterialIconStyles />
|
||||
<StyleInclude Source="avares://Starter/Styles/Carbon.axaml" />
|
||||
<StyleInclude Source="avares://Starter/Styles/Settings.axaml" />
|
||||
|
||||
<Style Selector="Button[(chrome|WindowDecorationProperties.ElementRole)=FullScreenButton]">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
|
||||
@@ -12,12 +12,27 @@ public sealed class Physics
|
||||
public double VMax;
|
||||
public double Length;
|
||||
public string LoadAccepted = "";
|
||||
public string LoadQ = "";
|
||||
public int MaxLoad;
|
||||
public int AllowedFlagA = 3;
|
||||
public int AllowedFlagB = 3;
|
||||
public string ControlTypeA = "";
|
||||
public string ControlTypeB = "";
|
||||
|
||||
public string EngineType = "";
|
||||
|
||||
/// <summary>
|
||||
/// True for the engine types the simulator actually runs its diesel heat model for.
|
||||
/// Mover.cpp calls dizel_Update - and through it dizel_Heat - only for DieselEngine
|
||||
/// and DieselElectric; LoadFIZ_EngineDecode maps the legacy "DumbDE" onto
|
||||
/// DieselElectric. On anything else the coolant temperature struct is written and
|
||||
/// never read.
|
||||
/// </summary>
|
||||
public bool IsDieselEngine =>
|
||||
EngineType.Equals("DieselEngine", StringComparison.OrdinalIgnoreCase)
|
||||
|| EngineType.Equals("DieselElectric", StringComparison.OrdinalIgnoreCase)
|
||||
|| EngineType.Equals("DumbDE", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static readonly Dictionary<string, Physics?> Cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static Dictionary<string, string>? _fizIndex;
|
||||
@@ -85,7 +100,8 @@ public sealed class Physics
|
||||
|
||||
private static readonly HashSet<string> KnownKeys = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"M", "Vmax", "L", "LoadAccepted", "MaxLoad", "AllowedFlag", "ControlType"
|
||||
"M", "Vmax", "L", "LoadAccepted", "LoadQ", "MaxLoad", "AllowedFlag", "ControlType",
|
||||
"EngineType"
|
||||
};
|
||||
|
||||
private static void Parse(Physics p, string path, string dir, string[]? prms, int depth)
|
||||
@@ -156,8 +172,12 @@ public sealed class Physics
|
||||
break;
|
||||
case "load":
|
||||
if (key.Equals("LoadAccepted", StringComparison.OrdinalIgnoreCase)) p.LoadAccepted = value;
|
||||
else if (key.Equals("LoadQ", StringComparison.OrdinalIgnoreCase)) p.LoadQ = value;
|
||||
else if (key.Equals("MaxLoad", StringComparison.OrdinalIgnoreCase)) p.MaxLoad = (int)ParseD(value, p.MaxLoad);
|
||||
break;
|
||||
case "engine":
|
||||
if (key.Equals("EngineType", StringComparison.OrdinalIgnoreCase)) p.EngineType = value;
|
||||
break;
|
||||
case "dimensions":
|
||||
if (key.Equals("L", StringComparison.OrdinalIgnoreCase)) p.Length = ParseD(value, p.Length);
|
||||
break;
|
||||
|
||||
@@ -95,9 +95,8 @@ public sealed class Settings
|
||||
public int VehiclesVolume = 100;
|
||||
public int PositionalVolume = 100;
|
||||
public int AmbientVolume = 100;
|
||||
public int VolumePaused = 100;
|
||||
public bool SkipPipeline;
|
||||
public string PyScreenPriority = "normal";
|
||||
public int PythonScreenUpdateRate = 200;
|
||||
public bool DebugLogVisible;
|
||||
private int _debugLogExtraBits;
|
||||
|
||||
@@ -123,6 +122,13 @@ public sealed class Settings
|
||||
public bool HideArchivalVehicles = true;
|
||||
public string LastScenery = "";
|
||||
|
||||
/// <summary>
|
||||
/// Starter's own window state, the way the old one kept WindowMaximized in
|
||||
/// starter.ini. Only the maximised flag is remembered - a normal window
|
||||
/// always opens at its design size, centred on the screen.
|
||||
/// </summary>
|
||||
public bool WindowMaximized;
|
||||
|
||||
public static readonly string[] RenderEngines =
|
||||
{ "full", "legacy", "simpleshader", "simple", "off", "experimental" };
|
||||
|
||||
@@ -339,7 +345,6 @@ public sealed class Settings
|
||||
public void SaveTo(string path)
|
||||
{
|
||||
WriteToConfig();
|
||||
ApplyFramebufferFidelity();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -385,21 +390,6 @@ public sealed class Settings
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFramebufferFidelity()
|
||||
{
|
||||
int fidelity = BufferScalePercent switch
|
||||
{
|
||||
<= 75 => 1,
|
||||
<= 125 => 2,
|
||||
<= 175 => 3,
|
||||
_ => 0
|
||||
};
|
||||
if (fidelity > 0)
|
||||
_config.SetInt("gfx.framebuffer.fidelity", fidelity);
|
||||
else
|
||||
_config.Remove("gfx.framebuffer.fidelity");
|
||||
}
|
||||
|
||||
public void DumpMissingVehicleLog()
|
||||
{
|
||||
if (!LogMissingVehicleFiles) return;
|
||||
@@ -523,9 +513,10 @@ public sealed class Settings
|
||||
VehiclesVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.vehicle", 1.0) * 100), 1, 100);
|
||||
PositionalVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.positional", 1.0) * 100), 1, 100);
|
||||
AmbientVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.ambient", 1.0) * 100), 1, 100);
|
||||
VolumePaused = Clamp((int)Math.Round(c.GetDouble("sound.volume.paused", 1.0) * 100), 0, 100);
|
||||
SkipPipeline = c.GetBool("gfx.skippipeline", false);
|
||||
PyScreenPriority = c.GetString("pyscreenrendererpriority", "normal");
|
||||
PythonScreenUpdateRate = c.Has("python.updatetime")
|
||||
? Clamp(c.GetInt("python.updatetime", 200), 0, 10000)
|
||||
: MapOldPyScreenPriority(c.GetString("pyscreenrendererpriority", "normal"));
|
||||
|
||||
CompressTextures = c.GetBool("compresstex", true);
|
||||
ScaleSpeculars = c.GetBool("scalespeculars", true);
|
||||
@@ -547,6 +538,7 @@ public sealed class Settings
|
||||
ShowArchivalSceneries = c.GetBool("starter.show.archival", true);
|
||||
HideArchivalVehicles = c.GetBool("starter.hide.archivalvehicles", true);
|
||||
LastScenery = c.GetString("starter.last.scenery", "");
|
||||
WindowMaximized = c.GetBool("starter.window.maximized", false);
|
||||
}
|
||||
|
||||
private static readonly string[] ObsoleteKeys =
|
||||
@@ -597,6 +589,10 @@ public sealed class Settings
|
||||
c.SetInt("width", Width);
|
||||
c.SetInt("height", Height);
|
||||
c.SetInt("starter.bufferscale", BufferScalePercent);
|
||||
int fbw = (int)Math.Round(Width * BufferScalePercent / 100.0);
|
||||
int fbh = (int)Math.Round(Height * BufferScalePercent / 100.0);
|
||||
c.SetInt("gfx.framebuffer.width", fbw);
|
||||
c.SetInt("gfx.framebuffer.height", fbh);
|
||||
c.SetInt("maxtexturesize", MaxTextureSize);
|
||||
c.SetInt("maxcabtexturesize", MaxCabTextureSize);
|
||||
c.SetInt("anisotropicfiltering", TextureFiltering);
|
||||
@@ -648,9 +644,8 @@ public sealed class Settings
|
||||
c.SetDouble("sound.volume.vehicle", VehiclesVolume / 100.0);
|
||||
c.SetDouble("sound.volume.positional", PositionalVolume / 100.0);
|
||||
c.SetDouble("sound.volume.ambient", AmbientVolume / 100.0);
|
||||
c.SetDouble("sound.volume.paused", VolumePaused / 100.0);
|
||||
c.SetBool("gfx.skippipeline", SkipPipeline);
|
||||
c.Set("pyscreenrendererpriority", PyScreenPriority);
|
||||
c.SetInt("python.updatetime", PythonScreenUpdateRate);
|
||||
|
||||
c.SetBool("compresstex", CompressTextures);
|
||||
c.SetBool("scalespeculars", ScaleSpeculars);
|
||||
@@ -675,6 +670,7 @@ public sealed class Settings
|
||||
c.Set("starter.last.scenery", LastScenery);
|
||||
else
|
||||
c.Remove("starter.last.scenery");
|
||||
c.SetBool("starter.window.maximized", WindowMaximized);
|
||||
}
|
||||
|
||||
private static int Clamp(int v, int lo, int hi) => v < lo ? lo : (v > hi ? hi : v);
|
||||
@@ -689,4 +685,14 @@ public sealed class Settings
|
||||
return i;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static int MapOldPyScreenPriority(string priority) =>
|
||||
priority.ToLowerInvariant() switch
|
||||
{
|
||||
"lower" => 400,
|
||||
"lowest" => 800,
|
||||
"idle" => 1600,
|
||||
"off" => 0,
|
||||
_ => 200
|
||||
};
|
||||
}
|
||||
|
||||
@@ -188,7 +188,17 @@ public class Dynamic
|
||||
|
||||
public const int PantStateMax = 3;
|
||||
|
||||
public int MaxLoad = -1;
|
||||
/// <summary>
|
||||
/// Lives in the coupling token as ".L<n>" - that is where the simulator reads
|
||||
/// it from (DynObj.cpp, the MoreParams loop). The older space-separated "L<n>"
|
||||
/// token in front of the load count is still read for legacy files, but never
|
||||
/// written back: the current scenario parser reads that slot as the load count.
|
||||
/// </summary>
|
||||
public int MaxLoad
|
||||
{
|
||||
get => Coupling.MaxLoad;
|
||||
set => Coupling.MaxLoad = value;
|
||||
}
|
||||
|
||||
public bool HasVelocity;
|
||||
|
||||
@@ -211,6 +221,9 @@ public class Dynamic
|
||||
{
|
||||
int p = 0;
|
||||
|
||||
// Legacy form: max load as its own token in front of the load count. The
|
||||
// current scenario parser reads that slot as an int, so this is read only -
|
||||
// the setter moves the value into the coupling token, where it is written.
|
||||
if (p < t.Count && t[p].Length > 0 && t[p][0] == 'L')
|
||||
{
|
||||
if (int.TryParse(t[p].AsSpan(1), NumberStyles.Integer,
|
||||
@@ -239,8 +252,6 @@ public class Dynamic
|
||||
internal string WriteTrailing()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (MaxLoad >= 0)
|
||||
sb.Append(" L").Append(MaxLoad.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
bool loaded = !string.IsNullOrEmpty(LoadType);
|
||||
sb.Append(' ').Append((loaded ? LoadCount : 0).ToString(CultureInfo.InvariantCulture));
|
||||
@@ -263,7 +274,6 @@ public class Dynamic
|
||||
Offset = Offset,
|
||||
DriverType = DriverType,
|
||||
Coupling = Coupling.Clone(),
|
||||
MaxLoad = MaxLoad,
|
||||
HasVelocity = HasVelocity,
|
||||
Velocity = Velocity,
|
||||
LoadCount = LoadCount,
|
||||
@@ -386,6 +396,28 @@ public sealed class Coupling
|
||||
Parameters.Add(wheels.ToParameter());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Max load override, the ".L<n>" sub-parameter. -1 means "not set", so the
|
||||
/// vehicle keeps the MaxLoad from its .fiz.
|
||||
/// </summary>
|
||||
public int MaxLoad
|
||||
{
|
||||
get => Parameters.Find(IsMaxLoad) is { } p
|
||||
&& int.TryParse(p.AsSpan(1), NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture, out int v) ? v : -1;
|
||||
set
|
||||
{
|
||||
Parameters.RemoveAll(IsMaxLoad);
|
||||
if (value >= 0)
|
||||
Parameters.Add("L" + value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsMaxLoad(string param) =>
|
||||
param.Length > 1
|
||||
&& char.ToUpperInvariant(param[0]) == 'L'
|
||||
&& param.Skip(1).All(char.IsDigit);
|
||||
|
||||
public bool ThermoDynamic
|
||||
{
|
||||
get => Parameters.Exists(p => p.Equals("TA", StringComparison.OrdinalIgnoreCase));
|
||||
@@ -414,11 +446,15 @@ public sealed class Coupling
|
||||
|
||||
public sealed class BrakeSetting
|
||||
{
|
||||
public static readonly string[] Modes = { "G", "P", "R", "M", "Q" };
|
||||
public static readonly string[] Modes = { "G", "P", "R", "M" };
|
||||
|
||||
public static readonly string[] Loads = { "T", "H", "F" };
|
||||
|
||||
public static readonly string[] Switches = { "0", "1" };
|
||||
// The simulator tests every letter of the B parameter independently, so the
|
||||
// "off" states are a separate axis from the G/P/R/M delay position. They stay
|
||||
// one combo because they contradict each other, but they no longer block a
|
||||
// delay position from being picked alongside them (e.g. "BRQ").
|
||||
public static readonly string[] Switches = { "<>", "0", "1", "X", "E", "Q" };
|
||||
|
||||
public string? Mode;
|
||||
|
||||
@@ -426,7 +462,9 @@ public sealed class BrakeSetting
|
||||
|
||||
public string? Switch;
|
||||
|
||||
public bool IsEmpty => Mode is null;
|
||||
// A brake switch or a load adaptation with no delay position is still a
|
||||
// parameter worth writing, so all three axes count towards emptiness.
|
||||
public bool IsEmpty => Mode is null && Load is null && Switch is null;
|
||||
|
||||
public static BrakeSetting? FromParameter(string? param)
|
||||
{
|
||||
@@ -436,7 +474,10 @@ public sealed class BrakeSetting
|
||||
string body = param.Substring(1).ToUpperInvariant();
|
||||
var b = new BrakeSetting
|
||||
{
|
||||
Mode = First(body, "M", "G", "P", "R", "Q"),
|
||||
// "M" first: the simulator reads it as R plus the magnetic rail brake,
|
||||
// so a bare "R" must not swallow it. Switches are matched in the
|
||||
// declared order too, so "<>" wins over the digits it cannot contain.
|
||||
Mode = First(body, "M", "G", "P", "R"),
|
||||
Load = First(body, Loads),
|
||||
Switch = First(body, Switches)
|
||||
};
|
||||
@@ -457,9 +498,18 @@ public sealed class WheelSettings
|
||||
|
||||
public int FlatnessRand;
|
||||
|
||||
public int FlatnessProb;
|
||||
/// <summary>
|
||||
/// Chance (%) that the flat spot is applied at all. The simulator defaults it
|
||||
/// to 100 when the P sub-parameter is missing, so that is the default here too
|
||||
/// - otherwise "0" would be written as "absent" and mean the exact opposite.
|
||||
/// </summary>
|
||||
public int FlatnessProb = DefaultFlatnessProb;
|
||||
|
||||
public bool IsEmpty => Sway <= 0 && Flatness <= 0 && FlatnessRand <= 0 && FlatnessProb <= 0;
|
||||
public const int DefaultFlatnessProb = 100;
|
||||
|
||||
// A probability on its own does nothing: without a flat size there is nothing
|
||||
// to roll for.
|
||||
public bool IsEmpty => Sway <= 0 && Flatness <= 0 && FlatnessRand <= 0;
|
||||
|
||||
public static WheelSettings? FromParameter(string? param)
|
||||
{
|
||||
@@ -492,7 +542,7 @@ public sealed class WheelSettings
|
||||
if (Sway > 0) sb.Append('H').Append(Sway);
|
||||
if (Flatness > 0) sb.Append('F').Append(Flatness);
|
||||
if (FlatnessRand > 0) sb.Append('R').Append(FlatnessRand);
|
||||
if (FlatnessProb > 0) sb.Append('P').Append(FlatnessProb);
|
||||
if (FlatnessProb != DefaultFlatnessProb) sb.Append('P').Append(FlatnessProb);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
71
StarterNG/Controls/SettingRow.cs
Normal file
71
StarterNG/Controls/SettingRow.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace StarterNG.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// One line of the settings panel: label on the left, the control itself in the
|
||||
/// middle, a value read-out on the right and an optional description underneath.
|
||||
/// Replaces the hand-rolled StackPanel/DockPanel pattern that used to be repeated
|
||||
/// for every setting.
|
||||
/// </summary>
|
||||
public class SettingRow : ContentControl
|
||||
{
|
||||
public static readonly StyledProperty<string?> LabelProperty =
|
||||
AvaloniaProperty.Register<SettingRow, string?>(nameof(Label));
|
||||
|
||||
public static readonly StyledProperty<string?> DescriptionProperty =
|
||||
AvaloniaProperty.Register<SettingRow, string?>(nameof(Description));
|
||||
|
||||
public static readonly StyledProperty<string?> ValueTextProperty =
|
||||
AvaloniaProperty.Register<SettingRow, string?>(nameof(ValueText));
|
||||
|
||||
public string? Label
|
||||
{
|
||||
get => GetValue(LabelProperty);
|
||||
set => SetValue(LabelProperty, value);
|
||||
}
|
||||
|
||||
public string? Description
|
||||
{
|
||||
get => GetValue(DescriptionProperty);
|
||||
set => SetValue(DescriptionProperty, value);
|
||||
}
|
||||
|
||||
public string? ValueText
|
||||
{
|
||||
get => GetValue(ValueTextProperty);
|
||||
set => SetValue(ValueTextProperty, value);
|
||||
}
|
||||
|
||||
public SettingRow()
|
||||
{
|
||||
UpdatePseudoClasses();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == LabelProperty ||
|
||||
change.Property == DescriptionProperty ||
|
||||
change.Property == ValueTextProperty ||
|
||||
change.Property == ContentProperty)
|
||||
UpdatePseudoClasses();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drives the template: an empty label lets the control span the label column,
|
||||
/// an empty description or value collapses that part of the row entirely.
|
||||
/// </summary>
|
||||
private void UpdatePseudoClasses()
|
||||
{
|
||||
PseudoClasses.Set(":no-label", string.IsNullOrEmpty(Label));
|
||||
PseudoClasses.Set(":no-description", string.IsNullOrEmpty(Description));
|
||||
PseudoClasses.Set(":no-value", string.IsNullOrEmpty(ValueText));
|
||||
// A read-only row is a label and a reading with no widget between them. The
|
||||
// reading then takes the middle column too, so a long path ellipsises instead
|
||||
// of demanding its full width from an Auto column and getting cut off.
|
||||
PseudoClasses.Set(":no-content", Content is null);
|
||||
}
|
||||
}
|
||||
21
StarterNG/Controls/SettingsSection.cs
Normal file
21
StarterNG/Controls/SettingsSection.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Primitives;
|
||||
|
||||
namespace StarterNG.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// A group of <see cref="SettingRow"/>s under a flat title with an accent rule.
|
||||
/// <see cref="Toggle"/> holds an optional master switch shown on the right of the
|
||||
/// title line; the section content gates itself off it.
|
||||
/// </summary>
|
||||
public class SettingsSection : HeaderedContentControl
|
||||
{
|
||||
public static readonly StyledProperty<object?> ToggleProperty =
|
||||
AvaloniaProperty.Register<SettingsSection, object?>(nameof(Toggle));
|
||||
|
||||
public object? Toggle
|
||||
{
|
||||
get => GetValue(ToggleProperty);
|
||||
set => SetValue(ToggleProperty, value);
|
||||
}
|
||||
}
|
||||
102
StarterNG/Infrastructure/Shortcuts.cs
Normal file
102
StarterNG/Infrastructure/Shortcuts.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace StarterNG.Infrastructure;
|
||||
|
||||
public enum ShortcutScope
|
||||
{
|
||||
/// <summary>Works anywhere in the window.</summary>
|
||||
Global,
|
||||
|
||||
/// <summary>Only while the depot page is on screen.</summary>
|
||||
Depot,
|
||||
|
||||
/// <summary>Handled by the consist strip itself, once a vehicle card has focus.</summary>
|
||||
Consist
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One row of the shortcut table. <see cref="Key"/> is <see cref="Avalonia.Input.Key.None"/>
|
||||
/// for rows that only document a gesture handled elsewhere (the consist strip reads the
|
||||
/// arrow keys itself, they are never dispatched from the window).
|
||||
/// </summary>
|
||||
public sealed record Shortcut(
|
||||
string Id,
|
||||
string Display,
|
||||
string LabelKey,
|
||||
ShortcutScope Scope,
|
||||
Key Key = Key.None,
|
||||
KeyModifiers Modifiers = KeyModifiers.None)
|
||||
{
|
||||
public bool IsDispatched => Key != Key.None;
|
||||
|
||||
public bool Matches(Key key, KeyModifiers modifiers) =>
|
||||
IsDispatched && Key == key && Modifiers == modifiers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The single source of truth for the starter's own shortcuts: the window dispatches from
|
||||
/// this table and the cheat sheet is printed from it, so the two cannot drift apart. These
|
||||
/// are deliberately hardcoded - the configurable key list in Settings belongs to the
|
||||
/// simulator's cab controls and mixing the two only confuses people.
|
||||
/// </summary>
|
||||
public static class Shortcuts
|
||||
{
|
||||
public const string Start = "start";
|
||||
public const string PageScenarios = "page.scenarios";
|
||||
public const string PageDepot = "page.depot";
|
||||
public const string PageSettings = "page.settings";
|
||||
public const string ShowHelp = "help";
|
||||
public const string AddVehicle = "vehicle.add";
|
||||
public const string RemoveVehicle = "vehicle.remove";
|
||||
public const string ClearConsist = "consist.clear";
|
||||
public const string SetNumber = "vehicle.number";
|
||||
public const string FocusSearch = "search";
|
||||
|
||||
public static readonly IReadOnlyList<Shortcut> All = new[]
|
||||
{
|
||||
new Shortcut(Start, "Ctrl+Enter", "Start", ShortcutScope.Global,
|
||||
Key.Enter, KeyModifiers.Control),
|
||||
new Shortcut(PageScenarios, "Ctrl+1", "NavScenarios", ShortcutScope.Global,
|
||||
Key.D1, KeyModifiers.Control),
|
||||
new Shortcut(PageDepot, "Ctrl+2", "NavDepot", ShortcutScope.Global,
|
||||
Key.D2, KeyModifiers.Control),
|
||||
new Shortcut(PageSettings, "Ctrl+3", "NavSettings", ShortcutScope.Global,
|
||||
Key.D3, KeyModifiers.Control),
|
||||
new Shortcut(ShowHelp, "F1", "ShortcutShowHelp", ShortcutScope.Global,
|
||||
Key.F1),
|
||||
|
||||
new Shortcut(FocusSearch, "Ctrl+F", "ShortcutFocusSearch", ShortcutScope.Depot,
|
||||
Key.F, KeyModifiers.Control),
|
||||
new Shortcut(AddVehicle, "Insert", "ShortcutAddVehicle", ShortcutScope.Depot,
|
||||
Key.Insert),
|
||||
new Shortcut(RemoveVehicle, "Delete", "RemoveVehicle", ShortcutScope.Depot,
|
||||
Key.Delete),
|
||||
new Shortcut(ClearConsist, "Ctrl+Delete", "ConsistRemoveAll", ShortcutScope.Depot,
|
||||
Key.Delete, KeyModifiers.Control),
|
||||
new Shortcut(SetNumber, "Ctrl+N", "ShortcutSetNumber", ShortcutScope.Depot,
|
||||
Key.N, KeyModifiers.Control),
|
||||
|
||||
new Shortcut("consist.prevnext", "← →", "ShortcutPrevNext", ShortcutScope.Consist),
|
||||
new Shortcut("consist.firstlast", "Home / End", "ShortcutFirstLast", ShortcutScope.Consist),
|
||||
new Shortcut("consist.member", "↑ ↓", "ShortcutMember", ShortcutScope.Consist),
|
||||
new Shortcut("consist.move", "Ctrl+← Ctrl+→", "ShortcutMoveVehicle", ShortcutScope.Consist),
|
||||
new Shortcut("consist.flip", "Space", "TipFlip", ShortcutScope.Consist),
|
||||
new Shortcut("consist.menu", "Enter", "ShortcutCardMenu", ShortcutScope.Consist)
|
||||
};
|
||||
|
||||
public static string? Match(Key key, KeyModifiers modifiers) =>
|
||||
All.FirstOrDefault(s => s.Matches(key, modifiers))?.Id;
|
||||
|
||||
public static IEnumerable<Shortcut> InScope(ShortcutScope scope) =>
|
||||
All.Where(s => s.Scope == scope);
|
||||
|
||||
public static string ScopeLabel(ShortcutScope scope) => scope switch
|
||||
{
|
||||
ShortcutScope.Depot => App.Loc["NavDepot"],
|
||||
ShortcutScope.Consist => App.Loc["Consist"],
|
||||
_ => App.Loc["ShortcutsGlobal"]
|
||||
};
|
||||
}
|
||||
@@ -10,13 +10,16 @@ public static class StatsBar
|
||||
public static void Fill(Panel host, IReadOnlyList<(string Label, string Value)> fields)
|
||||
{
|
||||
host.Children.Clear();
|
||||
foreach (var (label, value) in fields)
|
||||
for (int i = 0; i < fields.Count; i++)
|
||||
{
|
||||
var (label, value) = fields[i];
|
||||
var pair = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Spacing = 5,
|
||||
Margin = new Avalonia.Thickness(0, 0, 22, 0),
|
||||
// no trailing gap on the last pair - these sit in a panel title,
|
||||
// flush with its right edge
|
||||
Margin = new Avalonia.Thickness(0, 0, i == fields.Count - 1 ? 0 : 22, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
pair.Children.Add(new TextBlock
|
||||
|
||||
44
StarterNG/Infrastructure/TextMatch.cs
Normal file
44
StarterNG/Infrastructure/TextMatch.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace StarterNG.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Case- and diacritic-insensitive substring matching, so that typing
|
||||
/// "rozdzielczosc" in a search box still finds "Rozdzielczość".
|
||||
/// </summary>
|
||||
public static class TextMatch
|
||||
{
|
||||
public static bool Contains(string? haystack, string? needle)
|
||||
{
|
||||
if (string.IsNullOrEmpty(needle))
|
||||
return true;
|
||||
if (string.IsNullOrEmpty(haystack))
|
||||
return false;
|
||||
|
||||
return Fold(haystack).Contains(Fold(needle), StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lower-cases and strips combining marks, so "Ż" and "z" compare equal.
|
||||
/// Polish "ł" has no decomposed form, so it is mapped by hand.
|
||||
/// </summary>
|
||||
public static string Fold(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return string.Empty;
|
||||
|
||||
var decomposed = text.ToLowerInvariant().Normalize(NormalizationForm.FormD);
|
||||
var sb = new StringBuilder(decomposed.Length);
|
||||
|
||||
foreach (char c in decomposed)
|
||||
{
|
||||
if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.NonSpacingMark)
|
||||
continue;
|
||||
sb.Append(c == 'ł' ? 'l' : c);
|
||||
}
|
||||
|
||||
return sb.ToString().Normalize(NormalizationForm.FormC);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
xmlns:starterNg="clr-namespace:StarterNG"
|
||||
Title="Starter MaSzyna"
|
||||
Icon="/Assets/starter.ico"
|
||||
Width="1010" Height="920" MinWidth="1010" MinHeight="920"
|
||||
Width="1010" Height="768" MinWidth="1010" MinHeight="666"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
<DockPanel LastChildFill="True">
|
||||
@@ -20,7 +20,7 @@
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
BorderThickness="0,0,0,1"
|
||||
PointerPressed="TitleBar_OnPointerPressed">
|
||||
<Grid RowDefinitions="50">
|
||||
<Grid RowDefinitions="44">
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" Background="Transparent">
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
VerticalAlignment="Center" Margin="0,0,3,0" Spacing="0">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="0,0,6,0">
|
||||
<Label Content="{Binding [Version], Source={x:Static starterNg:App.Loc}}"
|
||||
FontSize="10" Foreground="{StaticResource CarbonFgDim}" Padding="0" />
|
||||
FontSize="12" Foreground="{StaticResource CarbonFgDim}" Padding="0" />
|
||||
<Label Content="{x:Static starterNg:AppInfo.Version}"
|
||||
FontSize="10" Foreground="{StaticResource CarbonFg}" Padding="0" />
|
||||
FontSize="12" Foreground="{StaticResource CarbonFg}" Padding="0" />
|
||||
</StackPanel>
|
||||
|
||||
<ComboBox x:Name="LangCombo" Width="88" VerticalAlignment="Center"
|
||||
@@ -85,10 +85,11 @@
|
||||
</Border>
|
||||
|
||||
<Button Name="startButton" DockPanel.Dock="Bottom" Classes="Start" Height="42" Margin="3"
|
||||
TabIndex="9"
|
||||
Click="StartButton_OnClick"
|
||||
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}" />
|
||||
|
||||
<Panel Margin="3,3,3,0">
|
||||
<Panel Margin="3,3,3,0" TabIndex="1">
|
||||
<views:Scenarios x:Name="ScenariosView" />
|
||||
<views:Depot x:Name="DepotView" IsVisible="False" />
|
||||
<views:Settings x:Name="SettingsView" IsVisible="False" />
|
||||
|
||||
@@ -10,6 +10,7 @@ using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Domain;
|
||||
using StarterNG.Infrastructure;
|
||||
@@ -34,6 +35,15 @@ public partial class MainWindow : Window
|
||||
ExtendClientAreaToDecorationsHint = true;
|
||||
ExtendClientAreaTitleBarHeightHint = TitleBarHeight;
|
||||
|
||||
if (SettingsModel.Instance.WindowMaximized)
|
||||
WindowState = WindowState.Maximized;
|
||||
|
||||
PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.Property == WindowStateProperty && WindowState != WindowState.Minimized)
|
||||
SettingsModel.Instance.WindowMaximized = WindowState == WindowState.Maximized;
|
||||
};
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
TopBar.Padding = new Thickness(0, TitleBarHeight, 0, 0);
|
||||
|
||||
@@ -43,6 +53,12 @@ public partial class MainWindow : Window
|
||||
startButton.AddHandler(PointerReleasedEvent, StartButton_OnRightClick,
|
||||
RoutingStrategies.Tunnel);
|
||||
|
||||
SettingsView.ThumbnailSizeChanged += () =>
|
||||
{
|
||||
DepotView.RefreshConsistView();
|
||||
ScenariosView.RefreshConsistView();
|
||||
};
|
||||
|
||||
ScenariosView.RandomizeTexturesRequested += async scenery =>
|
||||
{
|
||||
await DepotView.RandomizeSceneryTrainsetsAsync(scenery);
|
||||
@@ -54,6 +70,10 @@ public partial class MainWindow : Window
|
||||
|
||||
ApplyInitialNav();
|
||||
|
||||
// Tunnelling: the shortcuts are checked before any view sees the key, which is
|
||||
// why OnShortcutKey has to be careful about text entry and key capture.
|
||||
AddHandler(KeyDownEvent, OnShortcutKey, RoutingStrategies.Tunnel);
|
||||
|
||||
AppState.Instance.Changed += UpdateStartButton;
|
||||
Opened += (_, _) =>
|
||||
{
|
||||
@@ -89,6 +109,61 @@ public partial class MainWindow : Window
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnShortcutKey(object? sender, KeyEventArgs e)
|
||||
{
|
||||
// A cab-control binding in Settings is waiting for this very keypress.
|
||||
if (SettingsView?.IsCapturingKey == true)
|
||||
return;
|
||||
|
||||
var mods = e.KeyModifiers & (KeyModifiers.Control | KeyModifiers.Shift | KeyModifiers.Alt);
|
||||
if (Shortcuts.Match(e.Key, mods) is not { } id)
|
||||
return;
|
||||
|
||||
// Bare keys belong to whatever is being typed into; Ctrl/Alt combinations do not
|
||||
// collide with text entry, so those stay live everywhere.
|
||||
if (mods == KeyModifiers.None && IsTextEntryFocused())
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case Shortcuts.Start:
|
||||
_ = LaunchAsync(saveSettings: true, freeFly: false);
|
||||
break;
|
||||
|
||||
case Shortcuts.PageScenarios: NavScenarios.IsChecked = true; break;
|
||||
case Shortcuts.PageDepot: NavDepot.IsChecked = true; break;
|
||||
case Shortcuts.PageSettings: NavSettings.IsChecked = true; break;
|
||||
|
||||
case Shortcuts.ShowHelp:
|
||||
Views.ShortcutsWindow.Create().ShowDialog(this);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!DepotView.IsVisible)
|
||||
return;
|
||||
switch (id)
|
||||
{
|
||||
case Shortcuts.FocusSearch: DepotView.KeyFocusSearch(); break;
|
||||
case Shortcuts.AddVehicle: DepotView.KeyAddVehicle(); break;
|
||||
case Shortcuts.RemoveVehicle: DepotView.KeyRemoveVehicle(); break;
|
||||
case Shortcuts.ClearConsist: DepotView.KeyClearConsist(); break;
|
||||
case Shortcuts.SetNumber: DepotView.KeySetWagonNumber(); break;
|
||||
default: return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anything that swallows plain typing: text boxes, but also the editable inner box
|
||||
/// of a NumericUpDown or AutoCompleteBox.
|
||||
/// </summary>
|
||||
private bool IsTextEntryFocused() =>
|
||||
FocusManager?.GetFocusedElement() is Visual focused
|
||||
&& (focused is TextBox || focused.FindAncestorOfType<TextBox>() != null);
|
||||
|
||||
private void Nav_OnCheckedChanged(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not RadioButton { IsChecked: true } rb)
|
||||
@@ -164,6 +239,15 @@ public partial class MainWindow : Window
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
|
||||
var keys = new MenuItem
|
||||
{
|
||||
Header = App.Loc["ShortcutsTitle"],
|
||||
InputGesture = new KeyGesture(Key.F1)
|
||||
};
|
||||
keys.Click += (_, _) => Views.ShortcutsWindow.Create().ShowDialog(this);
|
||||
menu.Items.Add(keys);
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
var wiki = new MenuItem { Header = App.Loc["HelpWiki"] };
|
||||
wiki.Click += (_, _) => OpenPathOrUrl("https://wiki.eu07.pl/");
|
||||
menu.Items.Add(wiki);
|
||||
@@ -190,6 +274,13 @@ public partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
// "About" belongs with the other informational entries, not next to the
|
||||
// Save/Reset buttons of the settings panel where it used to sit.
|
||||
menu.Items.Add(new Separator());
|
||||
var about = new MenuItem { Header = App.Loc["About"] };
|
||||
about.Click += (_, _) => Views.AboutWindow.Create().ShowDialog(this);
|
||||
menu.Items.Add(about);
|
||||
|
||||
menu.Open(HelpButton);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"profiles": {
|
||||
"StarterNG": {
|
||||
"commandName": "Project",
|
||||
"workingDirectory": "/home/maj00r/.local/share/Steam/steamapps/common/MaSzyna/"
|
||||
"workingDirectory": "/home/maj00r/.local/share/Steam/steamapps/common/_MaSzyna/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,14 @@ public class LocalizationService : INotifyPropertyChanged
|
||||
|
||||
private readonly HashSet<string> _reportedMisses = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Keys with this suffix hold the optional one-line explanation shown under a
|
||||
/// setting. They are only authored in en/pl, so a miss yields an empty string
|
||||
/// (which collapses the description line) instead of the raw key, and is not
|
||||
/// reported as a translation gap.
|
||||
/// </summary>
|
||||
private const string OptionalSuffix = "Desc";
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
@@ -30,6 +38,9 @@ public class LocalizationService : INotifyPropertyChanged
|
||||
if (_strings.TryGetValue(key, out var value))
|
||||
return value;
|
||||
|
||||
if (key.EndsWith(OptionalSuffix, StringComparison.Ordinal))
|
||||
return string.Empty;
|
||||
|
||||
lock (_reportedMisses)
|
||||
if (_reportedMisses.Add(key))
|
||||
Infrastructure.Diagnostics.Log($"lang/{CurrentLangCode}: missing key \"{key}\"");
|
||||
@@ -38,6 +49,13 @@ public class LocalizationService : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a key that may legitimately be absent, without logging a miss.
|
||||
/// Returns an empty string when the current language does not define it.
|
||||
/// </summary>
|
||||
public string Opt(string key) =>
|
||||
_strings.TryGetValue(key, out var value) ? value : string.Empty;
|
||||
|
||||
public readonly record struct LanguageInfo(string Code, string Name, string Path);
|
||||
|
||||
public static IReadOnlyList<LanguageInfo> AvailableLanguages()
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<SolidColorBrush x:Key="CarbonPanelBg" Color="#1E2327" />
|
||||
<SolidColorBrush x:Key="CarbonHeaderBg" Color="#2A3036" />
|
||||
<SolidColorBrush x:Key="CarbonHoverBg" Color="#2A3036" />
|
||||
<!-- Control metrics live here, not in the views. -->
|
||||
<x:Double x:Key="CarbonControlHeight">24</x:Double>
|
||||
<x:Double x:Key="CarbonCheckSize">16</x:Double>
|
||||
|
||||
<SolidColorBrush x:Key="CarbonInputBg" Color="#262C31" />
|
||||
<SolidColorBrush x:Key="CarbonBorderLight" Color="#3A424A" />
|
||||
<SolidColorBrush x:Key="CarbonBorderDark" Color="#0E0F12" />
|
||||
@@ -32,7 +36,7 @@
|
||||
Background="{StaticResource CarbonHeaderBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="8,4">
|
||||
Padding="8,3">
|
||||
<DockPanel LastChildFill="True">
|
||||
<ContentPresenter DockPanel.Dock="Right"
|
||||
Content="{TemplateBinding Tag}"
|
||||
@@ -44,19 +48,171 @@
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<ContentPresenter Content="{TemplateBinding Content}"
|
||||
Margin="8" />
|
||||
Margin="6" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type CheckBox}" TargetType="CheckBox">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Padding" Value="8,0,0,0" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid ColumnDefinitions="Auto,*" Background="Transparent">
|
||||
<Border Name="box"
|
||||
Width="{StaticResource CarbonCheckSize}"
|
||||
Height="{StaticResource CarbonCheckSize}"
|
||||
VerticalAlignment="Center"
|
||||
Background="{StaticResource CarbonInputBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
BorderThickness="1" CornerRadius="2">
|
||||
<Path Name="glyph" Margin="3" Stretch="Uniform"
|
||||
Data="M 0,4 L 3,7 L 9,0"
|
||||
Stroke="White" StrokeThickness="2"
|
||||
StrokeLineCap="Round" StrokeJoin="Round"
|
||||
IsVisible="False" />
|
||||
</Border>
|
||||
<ContentPresenter Name="PART_ContentPresenter" Grid.Column="1"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^:pointerover /template/ Border#box">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentHover}" />
|
||||
</Style>
|
||||
<Style Selector="^:checked /template/ Border#box">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccent}" />
|
||||
</Style>
|
||||
<Style Selector="^:checked /template/ Path#glyph">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
<Style Selector="^:indeterminate /template/ Border#box">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccent}" />
|
||||
</Style>
|
||||
<Style Selector="^:disabled">
|
||||
<Setter Property="Opacity" Value="0.45" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="SpinnerButton" TargetType="RepeatButton">
|
||||
<Setter Property="Width" Value="20" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border Name="root" Background="Transparent">
|
||||
<ContentPresenter Content="{TemplateBinding Content}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^:pointerover /template/ Border#root">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonBorderLight}" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
<!-- The spinner frame, so the field and its two buttons read as one control
|
||||
the same shape and height as a combo box. -->
|
||||
<ControlTheme x:Key="{x:Type ButtonSpinner}" TargetType="ButtonSpinner">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonInputBg}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonBorderLight}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="2" />
|
||||
<Setter Property="MinHeight" Value="{StaticResource CarbonControlHeight}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
MinHeight="{TemplateBinding MinHeight}">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||
<ContentPresenter Name="PART_ContentPresenter" Grid.Column="0"
|
||||
Content="{TemplateBinding Content}"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalContentAlignment="Stretch" />
|
||||
<RepeatButton Name="PART_IncreaseButton" Grid.Column="1"
|
||||
Theme="{StaticResource SpinnerButton}"
|
||||
IsVisible="{TemplateBinding ShowButtonSpinner}">
|
||||
<Path Data="M 0,4 L 4,0 L 8,4" Stroke="{StaticResource CarbonFgDim}"
|
||||
StrokeThickness="1.4" />
|
||||
</RepeatButton>
|
||||
<RepeatButton Name="PART_DecreaseButton" Grid.Column="2"
|
||||
Theme="{StaticResource SpinnerButton}"
|
||||
IsVisible="{TemplateBinding ShowButtonSpinner}">
|
||||
<Path Data="M 0,0 L 4,4 L 8,0" Stroke="{StaticResource CarbonFgDim}"
|
||||
StrokeThickness="1.4" />
|
||||
</RepeatButton>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type NumericUpDown}" TargetType="NumericUpDown">
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<DataValidationErrors>
|
||||
<!-- The spinner draws the frame, the text box sits inside it
|
||||
borderless - same shape as a combo box. -->
|
||||
<ButtonSpinner Name="PART_Spinner"
|
||||
MinHeight="{StaticResource CarbonControlHeight}"
|
||||
Padding="0"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
Background="{StaticResource CarbonInputBg}"
|
||||
CornerRadius="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Center"
|
||||
AllowSpin="{TemplateBinding AllowSpin}"
|
||||
ShowButtonSpinner="{TemplateBinding ShowButtonSpinner}"
|
||||
ButtonSpinnerLocation="{TemplateBinding ButtonSpinnerLocation}">
|
||||
<TextBox Name="PART_TextBox"
|
||||
MinHeight="0"
|
||||
Padding="6,0"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center"
|
||||
Text="{TemplateBinding Text, Mode=TwoWay}"
|
||||
Watermark="{TemplateBinding Watermark}"
|
||||
IsReadOnly="{TemplateBinding IsReadOnly}"
|
||||
AcceptsReturn="False"
|
||||
TextWrapping="NoWrap" />
|
||||
</ButtonSpinner>
|
||||
</DataValidationErrors>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="NavTab" TargetType="RadioButton">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="MinWidth" Value="150" />
|
||||
<Setter Property="MinHeight" Value="50" />
|
||||
<Setter Property="MinHeight" Value="44" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
@@ -80,9 +236,9 @@
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
<ControlTheme x:Key="{x:Type TabItem}" TargetType="TabItem">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="26" />
|
||||
<Setter Property="Padding" Value="14,4" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="MinHeight" Value="22" />
|
||||
<Setter Property="Padding" Value="14,2" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
@@ -128,7 +284,7 @@
|
||||
<Setter Property="Background" Value="{StaticResource CarbonAppBg}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
||||
<Setter Property="FontFamily" Value="Tahoma, Segoe UI, Inter" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TabControl">
|
||||
@@ -137,50 +293,43 @@
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox">
|
||||
<Setter Property="MinHeight" Value="24" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="{StaticResource CarbonControlHeight}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="8,1" />
|
||||
<Setter Property="CornerRadius" Value="2" />
|
||||
</Style>
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="MinHeight" Value="24" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="{StaticResource CarbonControlHeight}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="6,1" />
|
||||
<Setter Property="CornerRadius" Value="2" />
|
||||
</Style>
|
||||
<Style Selector="NumericUpDown">
|
||||
<Setter Property="MinHeight" Value="26" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
<!-- Fluent gives the spinner and the check controls a 32 px floor of their own,
|
||||
inside the template, so setting MinHeight on the control alone does nothing. -->
|
||||
<!-- Height comes from the templated TextBox, like every other input. -->
|
||||
<Style Selector="Button">
|
||||
<Setter Property="MinHeight" Value="25" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="10,2" />
|
||||
<Setter Property="CornerRadius" Value="2" />
|
||||
</Style>
|
||||
<Style Selector="CheckBox">
|
||||
<Setter Property="MinHeight" Value="17" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
|
||||
<!-- Checklist rows only sit tighter; the size comes from the theme above. -->
|
||||
<Style Selector="CheckBox.Checklist">
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Height" Value="26" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="6,0,0,0" />
|
||||
</Style>
|
||||
<Style Selector="CheckBox.Checklist /template/ Grid">
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Margin" Value="0,1" />
|
||||
</Style>
|
||||
<Style Selector="CheckBox /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
<Style Selector="RadioButton">
|
||||
<Setter Property="MinHeight" Value="17" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="RadioButton /template/ Grid:not(#RootGrid)">
|
||||
<Setter Property="Height" Value="20" />
|
||||
</Style>
|
||||
<Style Selector="Label">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="0,2" />
|
||||
</Style>
|
||||
|
||||
@@ -235,6 +384,33 @@
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource CarbonFg}" />
|
||||
</Style>
|
||||
|
||||
<!-- Secondary button: says "this is here, but it is not the main move". Used for
|
||||
actions whose scope is wider than the thing you have selected. The state rules
|
||||
have to target the presenter, not the button - Fluent paints PART_ContentPresenter
|
||||
on hover/press/disable and would otherwise fill an outline button solid. That is
|
||||
why Accent and Basic are written this way too, while Flat gets away with setting
|
||||
Background on the control itself. -->
|
||||
<Style Selector="Button.Outline">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonBorderLight}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="2" />
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||
</Style>
|
||||
<Style Selector="Button.Outline /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="Button.Outline:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonHoverBg}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource CarbonFg}" />
|
||||
</Style>
|
||||
<Style Selector="Button.Outline:pressed /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonInputBg}" />
|
||||
</Style>
|
||||
<Style Selector="Button.Outline:disabled /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel.Field">
|
||||
<Setter Property="Spacing" Value="2" />
|
||||
</Style>
|
||||
@@ -244,8 +420,10 @@
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<!-- Accent is reserved for interactive state (checked box, slider fill,
|
||||
selected category, primary button). Read-outs are plain foreground. -->
|
||||
<Style Selector="TextBlock.FieldValue">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonAccentBright}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
@@ -254,7 +432,7 @@
|
||||
</Style>
|
||||
<Style Selector="TextBlock.FieldHint">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="FontStyle" Value="Italic" />
|
||||
</Style>
|
||||
<Style Selector="Separator">
|
||||
@@ -290,15 +468,47 @@
|
||||
<Style Selector="ListBox.Compact ListBoxItem">
|
||||
<Setter Property="Padding" Value="4,0" />
|
||||
<Setter Property="MinHeight" Value="17" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TreeView.Compact TreeViewItem">
|
||||
<Setter Property="MinHeight" Value="19" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="TreeView.Compact TreeViewItem /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="MinHeight" Value="19" />
|
||||
<Setter Property="Padding" Value="3,0" />
|
||||
</Style>
|
||||
|
||||
<!-- Keyboard focus. The default theme leaves the ring almost invisible on this
|
||||
palette, so everything that can take focus gets the same bright outline; the
|
||||
consist cards paint it as a box shadow because their border already carries
|
||||
the selection colour. -->
|
||||
<Style Selector=":is(Button):focus-visible /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="CheckBox:focus-visible /template/ Border#NormalRectangle">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
|
||||
</Style>
|
||||
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
|
||||
</Style>
|
||||
<Style Selector="ComboBox:focus-visible /template/ Border#Background">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:focus-visible">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonAccentBright}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:focus-visible /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="TreeViewItem:focus-visible /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="Border.ConsistCard:focus">
|
||||
<Setter Property="BoxShadow" Value="0 0 0 2 #41c400" />
|
||||
</Style>
|
||||
</Styles>
|
||||
|
||||
262
StarterNG/Styles/Settings.axaml
Normal file
262
StarterNG/Styles/Settings.axaml
Normal file
@@ -0,0 +1,262 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ctl="clr-namespace:StarterNG.Controls">
|
||||
<Styles.Resources>
|
||||
|
||||
<!-- One setting: [ label | control | value ] with the description on a
|
||||
second line spanning the whole row. Empty parts collapse, driven by
|
||||
the pseudo-classes SettingRow sets. -->
|
||||
<ControlTheme x:Key="{x:Type ctl:SettingRow}" TargetType="ctl:SettingRow">
|
||||
<Setter Property="Margin" Value="0,0,0,11" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- Every row is its own Grid, so the label column only
|
||||
lines up across rows when a shared-size scope makes
|
||||
them negotiate one width. The group is inert unless
|
||||
an ancestor declares Grid.IsSharedSizeScope, which is
|
||||
why this is safe to leave on for the settings page:
|
||||
there the pin on PART_Label below fixes the width
|
||||
instead. -->
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="SettingRowLabel" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Name="PART_Label"
|
||||
Grid.Row="0" Grid.Column="0"
|
||||
Text="{TemplateBinding Label}"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource CarbonFg}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="286" MaxWidth="286"
|
||||
Margin="0,0,14,0" />
|
||||
|
||||
<ContentPresenter Name="PART_Content"
|
||||
Grid.Row="0" Grid.Column="1"
|
||||
Content="{TemplateBinding Content}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock Name="PART_Value"
|
||||
Grid.Row="0" Grid.Column="2"
|
||||
Text="{TemplateBinding ValueText}"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource CarbonFg}"
|
||||
MinWidth="72"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="NoWrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,0,0" />
|
||||
|
||||
<TextBlock Name="PART_Description"
|
||||
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Text="{TemplateBinding Description}"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,3,0,0" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^:no-description /template/ TextBlock#PART_Description">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="^:no-value /template/ TextBlock#PART_Value">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="^:no-label /template/ TextBlock#PART_Label">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="^:no-content /template/ TextBlock#PART_Value">
|
||||
<Setter Property="Grid.Column" Value="1" />
|
||||
<Setter Property="Grid.ColumnSpan" Value="2" />
|
||||
</Style>
|
||||
<!-- A checkbox is its own label, so let it take the label column too.
|
||||
Alignment stays Stretch: a bare slider in a labelless row still
|
||||
wants the full width. -->
|
||||
<Style Selector="^:no-label /template/ ContentPresenter#PART_Content">
|
||||
<Setter Property="Grid.Column" Value="0" />
|
||||
<Setter Property="Grid.ColumnSpan" Value="2" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
<!-- Flat section: title, accent rule, content. No border, no filled header. -->
|
||||
<ControlTheme x:Key="{x:Type ctl:SettingsSection}" TargetType="ctl:SettingsSection">
|
||||
<Setter Property="Margin" Value="0,0,0,24" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<StackPanel>
|
||||
<DockPanel LastChildFill="True">
|
||||
<ContentPresenter DockPanel.Dock="Right"
|
||||
Content="{TemplateBinding Toggle}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Text="{TemplateBinding Header}"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource CarbonFg}"
|
||||
VerticalAlignment="Center" />
|
||||
</DockPanel>
|
||||
|
||||
<Grid Name="PART_Rule" ColumnDefinitions="28,*" Height="1" Margin="0,6,0,0">
|
||||
<Border Grid.Column="0" Background="{StaticResource CarbonAccentBright}" />
|
||||
<Border Grid.Column="1" Background="{StaticResource CarbonBorderLight}" />
|
||||
</Grid>
|
||||
|
||||
<ContentPresenter Name="PART_Content"
|
||||
Content="{TemplateBinding Content}"
|
||||
Margin="0,14,0,0" />
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<!-- Category list on the left of the settings panel. -->
|
||||
<ControlTheme x:Key="SettingsNavItem" TargetType="ListBoxItem">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Padding" Value="12,7" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border Name="root"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="3,0,0,0">
|
||||
<ContentPresenter Content="{TemplateBinding Content}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^:pointerover /template/ Border#root">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonHoverBg}" />
|
||||
</Style>
|
||||
<Style Selector="^:selected /template/ Border#root">
|
||||
<Setter Property="Background" Value="{StaticResource CarbonHoverBg}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
|
||||
</Style>
|
||||
<Style Selector="^:selected">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
</Styles.Resources>
|
||||
|
||||
<!-- Dimmed while a search finds nothing in this category. Still clickable:
|
||||
selecting it clears the search and opens the category. -->
|
||||
<Style Selector="ListBoxItem.NoHits">
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</Style>
|
||||
|
||||
<!-- Names the category above stacked search results. -->
|
||||
<Style Selector="TextBlock.CategoryCaption">
|
||||
<Setter Property="Foreground" Value="{StaticResource CarbonAccentBright}" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Margin" Value="0,0,0,8" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBox.SettingsNav">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
|
||||
<!-- Controls inside a row: keep them from stretching to absurd widths in the
|
||||
single-column layout, and give the row a predictable height. -->
|
||||
<Style Selector="ctl|SettingRow:not(.Compact) > ComboBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="MaxWidth" Value="360" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow:not(.Compact) > NumericUpDown">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="Width" Value="140" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow:not(.Compact) > Slider">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow:not(.Compact) > CheckBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow:not(.Compact) > Button">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="MinWidth" Value="180" />
|
||||
</Style>
|
||||
|
||||
<!-- Compact variant. The depot's detail panels are 250-390 px wide, where the
|
||||
settings page's rigid 300 px label column and 72 px value slot do not fit.
|
||||
Releasing the pin lets the shared-size group do its job: the column hugs the
|
||||
longest label in whichever panel it lands in, so no pixel count is picked
|
||||
here and it follows the translation. -->
|
||||
<Style Selector="ctl|SettingRow.Compact">
|
||||
<Setter Property="Margin" Value="0,0,0,5" />
|
||||
</Style>
|
||||
<!-- Right-aligned: an Auto column hugs the longest label, so anything shorter
|
||||
would otherwise hang in space away from its own control. MaxWidth stops one
|
||||
long translation from eating the panel - the label already wraps. -->
|
||||
<Style Selector="ctl|SettingRow.Compact /template/ TextBlock#PART_Label">
|
||||
<Setter Property="MinWidth" Value="96" />
|
||||
<Setter Property="MaxWidth" Value="170" />
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
<Setter Property="Margin" Value="0,0,10,0" />
|
||||
</Style>
|
||||
<!-- Reserved, not collapsed: keeping the unit gutter on every row means the
|
||||
controls share one right edge, and nothing shifts sideways when the unit
|
||||
disappears in pantograph mode. IsVisible deliberately overrides the theme's
|
||||
:no-value setter, which a file-level style is allowed to do. -->
|
||||
<Style Selector="ctl|SettingRow.Compact /template/ TextBlock#PART_Value">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
<Setter Property="MinWidth" Value="26" />
|
||||
<Setter Property="Margin" Value="6,0,0,0" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow.Compact /template/ TextBlock#PART_Description">
|
||||
<Setter Property="Margin" Value="0,2,0,0" />
|
||||
</Style>
|
||||
<!-- A checkbox lines up with the other controls here rather than escaping to the
|
||||
left edge the way it does on the wide settings page: in a narrow column the
|
||||
label edge reads as a rule, and a stray checkbox breaks it. Keeping the
|
||||
content out of column 0 also keeps a spanning child away from the
|
||||
shared-size column, which spares us the question of how the two interact. -->
|
||||
<Style Selector="ctl|SettingRow.Compact:no-label /template/ ContentPresenter#PART_Content">
|
||||
<Setter Property="Grid.Column" Value="1" />
|
||||
<Setter Property="Grid.ColumnSpan" Value="2" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctl|SettingRow.Compact > ComboBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow.Compact > NumericUpDown">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow.Compact > CheckBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow.Compact > Button">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow.Compact > StackPanel">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingRow.Compact > Grid">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ctl|SettingsSection.Compact">
|
||||
<Setter Property="Margin" Value="0,0,0,9" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingsSection.Compact /template/ ContentPresenter#PART_Content">
|
||||
<Setter Property="Margin" Value="0,6,0,0" />
|
||||
</Style>
|
||||
<Style Selector="ctl|SettingsSection.Compact /template/ Grid#PART_Rule">
|
||||
<Setter Property="Margin" Value="0,4,0,0" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -3,27 +3,35 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:starterNg="clr-namespace:StarterNG"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:views="clr-namespace:StarterNG.Views"
|
||||
x:CompileBindings="True"
|
||||
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
|
||||
x:Class="StarterNG.Views.Depot">
|
||||
<!-- TabIndex follows the workflow - pick a consist, browse, build the train,
|
||||
then the editors - not the column order the grid happens to have. -->
|
||||
<Grid Margin="0" ColumnSpacing="3" RowSpacing="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="270" MinWidth="220" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="340" MinWidth="300" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- The vehicle panel keeps its old height on a roomy window (MaxHeight)
|
||||
but shrinks with everything else towards the 666 px floor instead of
|
||||
being clipped. The consist row measures itself, so a whole card fits
|
||||
whether the thumbnails are the small ones or the large ones. -->
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" MinHeight="230" />
|
||||
<RowDefinition Height="275" MinHeight="260" />
|
||||
<RowDefinition Height="175" MinHeight="165" />
|
||||
<RowDefinition Height="*" MinHeight="150" />
|
||||
<RowDefinition Height="1.57*" MinHeight="120" MaxHeight="192" />
|
||||
<RowDefinition Height="Auto" MinHeight="140" MaxHeight="230" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Grid.Row="0" Background="{StaticResource CarbonPanelBg}"
|
||||
<Border Grid.Column="0" Grid.Row="0" TabIndex="2" Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||
CornerRadius="2" Padding="8">
|
||||
CornerRadius="2" Padding="6">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<DockPanel Grid.Row="0" Margin="0,0,0,6" LastChildFill="True">
|
||||
<CheckBox Name="hideArchivalCheck" DockPanel.Dock="Right" FontSize="11"
|
||||
<CheckBox Name="hideArchivalCheck" DockPanel.Dock="Right" FontSize="12"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"
|
||||
Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}"
|
||||
IsCheckedChanged="HideArchivalCheck_OnChanged" />
|
||||
@@ -34,10 +42,12 @@
|
||||
<Grid Grid.Row="1" RowDefinitions="Auto,Auto" RowSpacing="6" Margin="0,0,0,6">
|
||||
<DockPanel Grid.Row="0" LastChildFill="True">
|
||||
<Button Name="textureBaseButton" DockPanel.Dock="Right" Classes="Flat"
|
||||
Width="30" Margin="4,0,0,0" Cursor="Hand" Content="🔍"
|
||||
Width="30" Margin="4,0,0,0" Cursor="Hand"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="TextureBaseButton_OnClick"
|
||||
ToolTip.Tip="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
||||
ToolTip.Tip="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}">
|
||||
<materialIcons:MaterialIcon Kind="Magnify" Width="15" Height="15" />
|
||||
</Button>
|
||||
<ComboBox Name="categoryCombo" HorizontalAlignment="Stretch"
|
||||
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
||||
PointerWheelChanged="Combo_OnPointerWheel"
|
||||
@@ -49,7 +59,7 @@
|
||||
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
||||
</Grid>
|
||||
|
||||
<Border Name="miniPreviewPanel" Grid.Row="3" Height="66"
|
||||
<Border Name="miniPreviewPanel" Grid.Row="3" Height="46"
|
||||
Margin="0,6,0,0"
|
||||
Background="{StaticResource CarbonInputBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||
@@ -71,10 +81,15 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<HeaderedContentControl Name="mapConsistsPanel" Grid.Column="1" Grid.Row="0"
|
||||
Theme="{StaticResource GroupBox}">
|
||||
<!-- No group header here: the tab strip already names the list, and the
|
||||
scenery it belongs to is written into the tab itself. -->
|
||||
<Border Grid.Column="1" Grid.Row="0" TabIndex="1"
|
||||
Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
BorderThickness="1" CornerRadius="2" Padding="6">
|
||||
<TabControl>
|
||||
<TabItem Header="{Binding [SceneryConsists], Source={x:Static starterNg:App.Loc}}">
|
||||
<TabItem Name="sceneryConsistsTab"
|
||||
Header="{Binding [SceneryConsists], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Button Name="addToWarehouseButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||
@@ -83,10 +98,10 @@
|
||||
Content="{Binding [AddToWarehouse], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="16"
|
||||
Margin="2,6,0,0">
|
||||
<CheckBox Name="depotDrivableOnlyCheck" FontSize="11"
|
||||
<CheckBox Name="depotDrivableOnlyCheck" FontSize="12"
|
||||
IsCheckedChanged="ConsistFilter_OnChanged"
|
||||
Content="{Binding [ShowDrivableOnly], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox Name="depotShowAiCheck" FontSize="11"
|
||||
<CheckBox Name="depotShowAiCheck" FontSize="12"
|
||||
IsCheckedChanged="ConsistFilter_OnChanged"
|
||||
Content="{Binding [ShowAiVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
@@ -107,74 +122,107 @@
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</HeaderedContentControl>
|
||||
</Border>
|
||||
|
||||
<HeaderedContentControl Name="selectedVehiclePanel" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
Theme="{StaticResource GroupBox}">
|
||||
<Grid ColumnDefinitions="290*,3,380*,3,280*">
|
||||
<TabControl Grid.Column="0">
|
||||
<TabItem Header="{Binding [GeneralInfo], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True" Margin="4">
|
||||
<Button Name="removeVehicleButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="RemoveVehicleButton_OnClick"
|
||||
Content="{Binding [RemoveVehicle], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="generalPanel" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [AboutTexture], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="consistTexturePanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<TabControl Grid.Column="2">
|
||||
<!-- The configuration is the one panel here that is actually edited, so it
|
||||
gets a column of its own spanning both rows - from the top edge down to
|
||||
the consist - instead of a third of one squeezed row. -->
|
||||
<Border Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" TabIndex="6"
|
||||
Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
BorderThickness="1" CornerRadius="2" Padding="6">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Button Name="removeVehicleButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="RemoveVehicleButton_OnClick"
|
||||
Content="{Binding [RemoveVehicle], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TabControl>
|
||||
<TabItem Header="{Binding [Configuration], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="loadsPanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="brakesPanel" Margin="4" Spacing="6" />
|
||||
<StackPanel Name="loadsPanel" Margin="4" Grid.IsSharedSizeScope="True" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<TabControl Grid.Column="4">
|
||||
<TabItem Header="{Binding [Coupling], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="couplerPanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Damage], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="damagePanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</HeaderedContentControl>
|
||||
<!-- No title band either: the tab strips start straight away, and the
|
||||
vehicle being edited is the highlighted card in the consist below.
|
||||
Two cards, not one: the read-only pair (general info / texture) is
|
||||
visually separate from the editors (coupling / damage / brakes), and
|
||||
each lines up with the panel above it. -->
|
||||
<Border Grid.Row="1" Grid.Column="0" TabIndex="4"
|
||||
Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
BorderThickness="1" CornerRadius="2" Padding="6">
|
||||
<TabControl>
|
||||
<TabItem Header="{Binding [GeneralInfo], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="generalPanel" Margin="4" Grid.IsSharedSizeScope="True" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [AboutTexture], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="consistTexturePanel" Margin="4" Spacing="3" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Border>
|
||||
|
||||
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
|
||||
<Border Grid.Row="1" Grid.Column="1" TabIndex="5"
|
||||
Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||
BorderThickness="1" CornerRadius="2" Padding="6">
|
||||
<TabControl>
|
||||
<TabItem Header="{Binding [Coupling], Source={x:Static starterNg:App.Loc}}">
|
||||
<!-- The coupling grid now uses the full tab width. The two
|
||||
actions sit underneath, so they do not steal horizontal
|
||||
space from the checkbox columns. -->
|
||||
<DockPanel LastChildFill="True" Margin="4">
|
||||
<Panel Name="couplerActions" DockPanel.Dock="Bottom" />
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="couplerPanel" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Damage], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="damagePanel" Margin="4" Grid.IsSharedSizeScope="True" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<!-- The scope is what makes every row's label column in this
|
||||
panel settle on one width. -->
|
||||
<StackPanel Name="brakesPanel" Margin="4"
|
||||
Grid.IsSharedSizeScope="True" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Border>
|
||||
|
||||
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="3" TabIndex="3" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||
<!-- Mass, length and the rest ride in the title bar instead of taking
|
||||
a row of their own above the cards. -->
|
||||
<HeaderedContentControl.Tag>
|
||||
<StackPanel Name="trainStats" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" />
|
||||
</HeaderedContentControl.Tag>
|
||||
<DockPanel LastChildFill="True">
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
||||
<StackPanel Name="trainStats" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" />
|
||||
</DockPanel>
|
||||
<Panel Name="consistDropTarget">
|
||||
<TextBlock Name="emptyHint" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap" MaxWidth="520" Opacity="0.6" TextAlignment="Center"
|
||||
Text="{Binding [ConsistEmpty], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ScrollViewer Name="consistScroll"
|
||||
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled"
|
||||
Padding="0,0,0,4">
|
||||
<Panel>
|
||||
<!-- Tab reaches the strip once and lands on the selected
|
||||
card; the arrow keys walk it from there. -->
|
||||
<StackPanel Name="consistStack" Orientation="Horizontal"
|
||||
KeyboardNavigation.TabNavigation="Once"
|
||||
VerticalAlignment="Center" Spacing="0" />
|
||||
<Canvas Name="consistOverlay" IsHitTestVisible="False" />
|
||||
</Panel>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
@@ -76,8 +77,11 @@ public partial class Depot : UserControl
|
||||
_cards = new VehicleCards(consistStack, _db, _consist, _cargo, _minis, _hand,
|
||||
RebuildConsist, () => _details.Refresh(), _browser.SelectInBrowser, _drag.ArmCardDrag);
|
||||
|
||||
// Hidden still scrolls - it only drops the bar itself.
|
||||
MiniTextures.Sharp(miniPreview);
|
||||
|
||||
_details = new VehicleDetails(
|
||||
selectedVehiclePanel, generalPanel, consistTexturePanel, couplerPanel,
|
||||
generalPanel, consistTexturePanel, couplerPanel, couplerActions,
|
||||
brakesPanel, loadsPanel, damagePanel, removeVehicleButton,
|
||||
_db, _info, _consist, _cargo, _hand,
|
||||
RebuildConsist, _cards.RefreshMemberChrome, _browser.ShowTextureInfo);
|
||||
@@ -163,6 +167,9 @@ public partial class Depot : UserControl
|
||||
_details.Refresh();
|
||||
}
|
||||
|
||||
/// <summary>Redraws the consist cards, e.g. after the thumbnail size changed.</summary>
|
||||
public void RefreshConsistView() => RebuildConsist();
|
||||
|
||||
private void RebuildConsist()
|
||||
{
|
||||
consistStack.Children.Clear();
|
||||
@@ -183,6 +190,10 @@ public partial class Depot : UserControl
|
||||
_details.Refresh();
|
||||
UpdateTrainStats();
|
||||
WriteBackToScenery();
|
||||
|
||||
// The cards are thrown away and rebuilt on every change, so a keyboard-driven
|
||||
// edit has to be handed its card back or focus escapes the strip.
|
||||
_cards.FocusSelectedCard(onlyIfRequested: true);
|
||||
}
|
||||
|
||||
private void WriteBackToScenery()
|
||||
@@ -232,9 +243,9 @@ public partial class Depot : UserControl
|
||||
depotDrivableOnlyCheck.IsChecked = StarterNG.Classes.Settings.Instance.DrivableOnly;
|
||||
|
||||
_listScenery = AppState.Instance.CurrentScenery ?? _sceneries.FirstOrDefault();
|
||||
mapConsistsPanel.Header = _listScenery != null
|
||||
? $"{App.Loc["MapConsists"]}: {Path.GetFileNameWithoutExtension(_listScenery.Path)}"
|
||||
: App.Loc["NoSceneryConsists"];
|
||||
sceneryConsistsTab.Header = App.Loc["SceneryConsists"];
|
||||
ToolTip.SetTip(sceneryConsistsTab,
|
||||
_listScenery != null ? Path.GetFileNameWithoutExtension(_listScenery.Path) : App.Loc["NoSceneryConsists"]);
|
||||
|
||||
if (_listScenery != null)
|
||||
{
|
||||
@@ -425,6 +436,25 @@ public partial class Depot : UserControl
|
||||
AddTexture(_browser.Selected);
|
||||
}
|
||||
|
||||
/// <summary>Keyboard shortcut entry points. See <see cref="Infrastructure.Shortcuts"/>.</summary>
|
||||
public void KeyAddVehicle() => AddVehicleButton_OnClick(null, null!);
|
||||
|
||||
public void KeyRemoveVehicle() => RemoveVehicleButton_OnClick(null, null!);
|
||||
|
||||
public void KeyClearConsist()
|
||||
{
|
||||
if (_consist.EditingTrainset is { } ts && ts.Vehicles.Count > 0)
|
||||
RemoveAllVehicles(ts);
|
||||
}
|
||||
|
||||
public void KeySetWagonNumber() => _cards.ShowNumberFlyoutForSelection();
|
||||
|
||||
public void KeyFocusSearch()
|
||||
{
|
||||
searchBox.Focus();
|
||||
searchBox.SelectAll();
|
||||
}
|
||||
|
||||
public void ToolAddAllCategory() => AddAllCategoryButton_OnClick(null, null!);
|
||||
|
||||
public void ToolAddUniqueMmd() => AddUniqueMmdButton_OnClick(null, null!);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
using StarterNG.Classes;
|
||||
@@ -10,6 +12,18 @@ public sealed class MiniTextures
|
||||
{
|
||||
private readonly Dictionary<string, Bitmap?> _cache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Draws a mini without smoothing. These files are about 80x30 and get shown up
|
||||
/// to 68 px tall, so bilinear just smears the upscale; nearest keeps the edges.
|
||||
/// (Has to be set in code - Avalonia 12 exposes RenderOptions as a struct with
|
||||
/// Get/Set helpers, not as a styleable attached property.)
|
||||
/// </summary>
|
||||
public static Image Sharp(Image image)
|
||||
{
|
||||
RenderOptions.SetBitmapInterpolationMode(image, BitmapInterpolationMode.None);
|
||||
return image;
|
||||
}
|
||||
|
||||
public Bitmap? Get(string? name, int height, bool strict = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
@@ -27,8 +41,17 @@ public sealed class MiniTextures
|
||||
{
|
||||
try
|
||||
{
|
||||
// Decode at native size and let the renderer scale. The minis are
|
||||
// about 80x30, so DecodeToHeight would bake a 2x upscale into the
|
||||
// cache - blurry, and five times the memory. Only shrink here.
|
||||
using var fs = File.OpenRead(path);
|
||||
bmp = Bitmap.DecodeToHeight(fs, height);
|
||||
bmp = new Bitmap(fs);
|
||||
if (bmp.PixelSize.Height > height)
|
||||
{
|
||||
bmp.Dispose();
|
||||
fs.Position = 0;
|
||||
bmp = Bitmap.DecodeToHeight(fs, height);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
|
||||
@@ -222,13 +222,13 @@ public sealed class VehicleBrowser
|
||||
};
|
||||
var bmp = _minis.Get(cls, height);
|
||||
if (bmp != null)
|
||||
cell.Children.Add(new Image
|
||||
cell.Children.Add(MiniTextures.Sharp(new Image
|
||||
{
|
||||
Source = bmp, Height = height,
|
||||
Stretch = Stretch.Uniform,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
}));
|
||||
cell.Children.Add(new TextBlock
|
||||
{
|
||||
Text = cls,
|
||||
@@ -421,7 +421,7 @@ public sealed class VehicleBrowser
|
||||
target.Children.Add(new TextBlock
|
||||
{
|
||||
Text = line,
|
||||
FontSize = 10,
|
||||
FontSize = 12,
|
||||
Opacity = 0.85,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis
|
||||
});
|
||||
|
||||
@@ -8,6 +8,9 @@ public static class VehicleCardStyle
|
||||
public static readonly IBrush BorderSelected = new SolidColorBrush(Color.Parse("#AA41C400"));
|
||||
public static readonly IBrush BackgroundSelected = new SolidColorBrush(Color.Parse("#2241C400"));
|
||||
|
||||
/// <summary>Lit state of the flip button on a reversed vehicle.</summary>
|
||||
public static readonly IBrush Flipped = new SolidColorBrush(Color.Parse("#41C400"));
|
||||
|
||||
public static readonly IBrush Placeholder = new SolidColorBrush(Color.Parse("#22808080"));
|
||||
|
||||
public static int ThumbHeight =>
|
||||
|
||||
@@ -7,6 +7,8 @@ using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.VisualTree;
|
||||
using Material.Icons;
|
||||
using Material.Icons.Avalonia;
|
||||
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Domain;
|
||||
@@ -75,7 +77,10 @@ public sealed class VehicleCards
|
||||
?? _minis.Get(car.MiniName, VehicleCardStyle.ThumbHeight);
|
||||
if (bmp != null)
|
||||
{
|
||||
var img = new Image { Source = bmp, Height = VehicleCardStyle.ThumbHeight, Stretch = Stretch.Uniform };
|
||||
var img = MiniTextures.Sharp(new Image
|
||||
{
|
||||
Source = bmp, Height = VehicleCardStyle.ThumbHeight, Stretch = Stretch.Uniform
|
||||
});
|
||||
if (item.Flipped)
|
||||
{
|
||||
img.RenderTransform = new ScaleTransform(-1, 1);
|
||||
@@ -101,57 +106,50 @@ public sealed class VehicleCards
|
||||
}
|
||||
}
|
||||
|
||||
var nameBlock = new TextBlock
|
||||
{
|
||||
Text = item.Grouped && item.Cars.Count > 1 ? Consist.UnitLabel(item) : string.Empty,
|
||||
FontSize = 11,
|
||||
MaxWidth = Math.Max(120, item.Cars.Count * 96),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
IsVisible = item.Grouped && item.Cars.Count > 1
|
||||
};
|
||||
|
||||
var controls = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Spacing = 2,
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
Spacing = 3,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 1, 0, 2)
|
||||
};
|
||||
controls.Children.Add(SmallButton("◀", App.Loc["TipMoveLeft"], () => _consist.MoveLeft(item)));
|
||||
controls.Children.Add(DriverButton(item));
|
||||
controls.Children.Add(SmallButton("⇄", App.Loc["TipFlip"], () => _consist.Flip(item)));
|
||||
controls.Children.Add(SmallButton("▶", App.Loc["TipMoveRight"], () => _consist.MoveRight(item)));
|
||||
controls.Children.Add(FlipButton(item));
|
||||
|
||||
if (item.Grouped && item.Cars.Count > 1)
|
||||
controls.Children.Add(SmallButton("⧉", App.Loc["TipSplit"], () => _consist.Split(item)));
|
||||
controls.Children.Add(SmallButton(MaterialIconKind.CallSplit, App.Loc["TipSplit"], () => _consist.Split(item)));
|
||||
else if ((index + 1 < _consist.Count &&
|
||||
(Consist.HoldsTail(item) || _consist.CanFormUnit(item, _consist[index + 1]))) ||
|
||||
(index > 0 && Consist.HoldsTail(_consist[index - 1])))
|
||||
controls.Children.Add(SmallButton("⛓", App.Loc["TipJoin"], () => _consist.Join(item)));
|
||||
controls.Children.Add(SmallButton(MaterialIconKind.Link, App.Loc["TipJoin"], () => _consist.Join(item)));
|
||||
|
||||
var remove = SmallButton("✕", App.Loc["RemoveSelectedVehicle"], () => _consist.Remove(item));
|
||||
var remove = SmallButton(MaterialIconKind.Close, App.Loc["RemoveSelectedVehicle"], () => _consist.Remove(item));
|
||||
remove.Foreground = new SolidColorBrush(Color.Parse("#E05252"));
|
||||
remove.FontWeight = FontWeight.Bold;
|
||||
controls.Children.Add(remove);
|
||||
|
||||
var inner = new StackPanel { Spacing = 4 };
|
||||
var inner = new StackPanel { Spacing = 3 };
|
||||
inner.Children.Add(minis);
|
||||
inner.Children.Add(nameBlock);
|
||||
inner.Children.Add(controls);
|
||||
|
||||
var card = new Border
|
||||
{
|
||||
Margin = new Thickness(2, 0),
|
||||
Padding = new Thickness(6),
|
||||
Margin = new Thickness(3, 2, 3, 6),
|
||||
Padding = new Thickness(selected ? 4 : 5),
|
||||
CornerRadius = new CornerRadius(6),
|
||||
BorderThickness = new Thickness(selected ? 2 : 1),
|
||||
BorderBrush = selected ? VehicleCardStyle.BorderSelected : VehicleCardStyle.Border,
|
||||
Background = selected ? VehicleCardStyle.BackgroundSelected : Brushes.Transparent,
|
||||
Cursor = _hand,
|
||||
Child = inner,
|
||||
Tag = item
|
||||
Tag = item,
|
||||
// Focusable so the strip can be reached with Tab and walked with the
|
||||
// arrow keys; the focus ring itself comes from Carbon.axaml.
|
||||
Focusable = true
|
||||
};
|
||||
card.Classes.Add("ConsistCard");
|
||||
card.GotFocus += (_, _) => Select(item, null);
|
||||
card.KeyDown += (_, e) => OnCardKey(card, item, e);
|
||||
card.PointerPressed += (_, e) =>
|
||||
{
|
||||
|
||||
@@ -167,6 +165,7 @@ public sealed class VehicleCards
|
||||
_consist.SyncStartingVehicle();
|
||||
RefreshCardSelectionChrome();
|
||||
_refreshDetails();
|
||||
_selectInBrowser(_consist.SelectedCar);
|
||||
|
||||
_armDrag(index, e);
|
||||
};
|
||||
@@ -178,31 +177,30 @@ public sealed class VehicleCards
|
||||
TrainsetDisplay.VehicleLabel(car,
|
||||
head: index == 0 && ReferenceEquals(car, Consist.HeadCar(item)) && TrainsetDisplay.IsPowered(car));
|
||||
|
||||
/// <summary>
|
||||
/// The name is not printed under the thumbnail - it costs a row on every card
|
||||
/// and the tooltip says the same thing when it is actually wanted.
|
||||
/// </summary>
|
||||
private Control MemberFrame(Control visual, ConsistItem item, Dynamic car, string caption)
|
||||
{
|
||||
var stack = new StackPanel { Spacing = 2 };
|
||||
stack.Children.Add(visual);
|
||||
stack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = caption,
|
||||
FontSize = 10,
|
||||
MaxWidth = 96,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
});
|
||||
|
||||
var frame = new Border
|
||||
{
|
||||
Child = stack,
|
||||
Child = visual,
|
||||
Padding = new Thickness(1),
|
||||
BorderThickness = new Thickness(0, 2),
|
||||
BorderBrush = Brushes.Transparent,
|
||||
Cursor = _hand
|
||||
};
|
||||
|
||||
var lines = new List<string> { caption };
|
||||
if (!string.IsNullOrWhiteSpace(car.Name) && car.Name != caption)
|
||||
ToolTip.SetTip(frame, car.Name);
|
||||
lines.Add(car.Name);
|
||||
if (item.Grouped && item.Cars.Count > 1)
|
||||
lines.Add(Consist.UnitLabel(item));
|
||||
|
||||
string tip = string.Join("\n", lines.Where(l => !string.IsNullOrWhiteSpace(l)));
|
||||
if (tip.Length > 0)
|
||||
ToolTip.SetTip(frame, tip);
|
||||
|
||||
var picked = car;
|
||||
frame.PointerPressed += (_, _) => PressedCar = picked;
|
||||
@@ -233,12 +231,154 @@ public sealed class VehicleCards
|
||||
if (child is not Border { Tag: ConsistItem ci } border)
|
||||
continue;
|
||||
bool sel = ReferenceEquals(ci, _consist.Selected);
|
||||
border.Padding = new Thickness(sel ? 4 : 5);
|
||||
border.BorderThickness = new Thickness(sel ? 2 : 1);
|
||||
border.BorderBrush = sel ? VehicleCardStyle.BorderSelected : VehicleCardStyle.Border;
|
||||
border.Background = sel ? VehicleCardStyle.BackgroundSelected : Brushes.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects an item without going through a redraw. <paramref name="car"/> picks the
|
||||
/// active member of a multi-vehicle unit; null keeps the current one when possible.
|
||||
/// </summary>
|
||||
private void Select(ConsistItem item, Dynamic? car)
|
||||
{
|
||||
var target = car ?? (ReferenceEquals(_consist.Selected, item)
|
||||
? _consist.ActiveCar(item)
|
||||
: item.Cars[0]);
|
||||
|
||||
if (ReferenceEquals(_consist.Selected, item) && ReferenceEquals(_consist.SelectedCar, target))
|
||||
return;
|
||||
|
||||
_consist.Selected = item;
|
||||
_consist.SelectedCar = target;
|
||||
_consist.SyncStartingVehicle();
|
||||
RefreshCardSelectionChrome();
|
||||
_refreshDetails();
|
||||
_selectInBrowser(target);
|
||||
}
|
||||
|
||||
private Border? CardFor(ConsistItem item) =>
|
||||
consistStack.Children.OfType<Border>()
|
||||
.FirstOrDefault(b => ReferenceEquals(b.Tag, item));
|
||||
|
||||
/// <summary>
|
||||
/// Set before an action that rebuilds the strip, so focus lands back on the card
|
||||
/// instead of falling out to whatever comes next in the tab order.
|
||||
/// </summary>
|
||||
private bool _refocusAfterRedraw;
|
||||
|
||||
public void FocusSelectedCard(bool onlyIfRequested = false)
|
||||
{
|
||||
if (onlyIfRequested && !_refocusAfterRedraw)
|
||||
return;
|
||||
|
||||
_refocusAfterRedraw = false;
|
||||
if (_consist.Selected is { } item && CardFor(item) is { } card)
|
||||
{
|
||||
card.Focus();
|
||||
card.BringIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
private void Redraw(bool keepFocus)
|
||||
{
|
||||
_refocusAfterRedraw = keepFocus;
|
||||
_redraw();
|
||||
}
|
||||
|
||||
private void OnCardKey(Border card, ConsistItem item, KeyEventArgs e)
|
||||
{
|
||||
int index = _consist.IndexOf(item);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
bool ctrl = e.KeyModifiers.HasFlag(KeyModifiers.Control);
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Left when ctrl:
|
||||
_consist.MoveLeft(item);
|
||||
Redraw(keepFocus: true);
|
||||
break;
|
||||
|
||||
case Key.Right when ctrl:
|
||||
_consist.MoveRight(item);
|
||||
Redraw(keepFocus: true);
|
||||
break;
|
||||
|
||||
case Key.Left:
|
||||
case Key.Right:
|
||||
FocusCardAt(index + (e.Key == Key.Left ? -1 : 1));
|
||||
break;
|
||||
|
||||
case Key.Home:
|
||||
FocusCardAt(0);
|
||||
break;
|
||||
|
||||
case Key.End:
|
||||
FocusCardAt(_consist.Count - 1);
|
||||
break;
|
||||
|
||||
// Within a coupled unit the arrow keys pick which member the detail
|
||||
// panels talk about - the same thing clicking a thumbnail does.
|
||||
case Key.Up:
|
||||
case Key.Down:
|
||||
StepMember(item, e.Key == Key.Down ? 1 : -1);
|
||||
break;
|
||||
|
||||
case Key.Space:
|
||||
_consist.Flip(item);
|
||||
Redraw(keepFocus: true);
|
||||
break;
|
||||
|
||||
case Key.Enter:
|
||||
card.ContextMenu?.Open(card);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void FocusCardAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= _consist.Count)
|
||||
return;
|
||||
|
||||
var item = _consist[index];
|
||||
Select(item, null);
|
||||
if (CardFor(item) is { } card)
|
||||
{
|
||||
card.Focus();
|
||||
// Focusing alone does not scroll a plain Border into view.
|
||||
card.BringIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
private void StepMember(ConsistItem item, int delta)
|
||||
{
|
||||
if (item.Cars.Count < 2)
|
||||
return;
|
||||
|
||||
int at = item.Cars.IndexOf(_consist.ActiveCar(item));
|
||||
int next = Math.Clamp(at + delta, 0, item.Cars.Count - 1);
|
||||
if (next != at)
|
||||
Select(item, item.Cars[next]);
|
||||
}
|
||||
|
||||
/// <summary>Ctrl+N on the selected card, from the window's shortcut table.</summary>
|
||||
public void ShowNumberFlyoutForSelection()
|
||||
{
|
||||
if (_consist.Selected is not { } item || CardFor(item) is not { } card)
|
||||
return;
|
||||
|
||||
ShowWagonNumberFlyout(card, _consist.ActiveCar(item));
|
||||
}
|
||||
|
||||
private void ShowWagonNumberFlyout(Control anchor, Dynamic car)
|
||||
{
|
||||
var num = new NumericUpDown
|
||||
@@ -276,7 +416,7 @@ public sealed class VehicleCards
|
||||
};
|
||||
var coupler = new Button
|
||||
{
|
||||
Padding = new Thickness(2),
|
||||
Padding = new Thickness(4, 6, 4, 6),
|
||||
MinWidth = 0,
|
||||
Cursor = _hand,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
@@ -288,7 +428,7 @@ public sealed class VehicleCards
|
||||
}
|
||||
};
|
||||
coupler.Classes.Add("Basic");
|
||||
ToolTip.SetTip(coupler, App.Loc["Coupling"]);
|
||||
ToolTip.SetTip(coupler, $"{App.Loc["Coupling"]} — {App.Loc["TipReorder"]}");
|
||||
return coupler;
|
||||
}
|
||||
|
||||
@@ -302,7 +442,7 @@ public sealed class VehicleCards
|
||||
var copy = new Button
|
||||
{
|
||||
Content = "\u00AB",
|
||||
FontSize = 13,
|
||||
FontSize = 12,
|
||||
Padding = new Thickness(6, 1),
|
||||
Margin = new Thickness(12, 0, 0, 0),
|
||||
IsEnabled = unitIdx > 0,
|
||||
@@ -324,11 +464,20 @@ public sealed class VehicleCards
|
||||
{
|
||||
Text = App.Loc["Coupling"],
|
||||
FontWeight = FontWeight.Bold,
|
||||
FontSize = 11,
|
||||
FontSize = 12,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
panel.Children.Add(header);
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
ColumnDefinitions = new ColumnDefinitions("*,*"),
|
||||
RowSpacing = 2,
|
||||
ColumnSpacing = 12
|
||||
};
|
||||
for (int r = 0; r < (CouplingBits.BitKeys.Length + 1) / 2; r++)
|
||||
grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
||||
|
||||
for (int i = 0; i < CouplingBits.BitKeys.Length; i++)
|
||||
{
|
||||
int bit = 1 << i;
|
||||
@@ -336,17 +485,20 @@ public sealed class VehicleCards
|
||||
{
|
||||
Content = App.Loc[CouplingBits.BitKeys[i]],
|
||||
IsChecked = d.Coupling.Has(bit),
|
||||
FontSize = 11
|
||||
FontSize = 12
|
||||
};
|
||||
check.IsCheckedChanged += (_, _) => d.Coupling.Set(bit, check.IsChecked == true);
|
||||
panel.Children.Add(check);
|
||||
Grid.SetRow(check, i / 2);
|
||||
Grid.SetColumn(check, i % 2);
|
||||
grid.Children.Add(check);
|
||||
}
|
||||
panel.Children.Add(grid);
|
||||
|
||||
var thermo = new CheckBox
|
||||
{
|
||||
Content = App.Loc["ThermoAmbient"],
|
||||
IsChecked = d.Coupling.ThermoDynamic,
|
||||
FontSize = 11,
|
||||
FontSize = 12,
|
||||
Margin = new Thickness(0, 2, 0, 0)
|
||||
};
|
||||
thermo.IsCheckedChanged += (_, _) => d.Coupling.ThermoDynamic = thermo.IsChecked == true;
|
||||
@@ -355,18 +507,26 @@ public sealed class VehicleCards
|
||||
return new Border { Padding = new Thickness(8), Child = panel };
|
||||
}
|
||||
|
||||
private const string TextPresentation = "\uFE0E";
|
||||
/// <summary>
|
||||
/// Card buttons draw vector icons rather than glyphs - Windows renders half
|
||||
/// the arrow and link characters as colour emoji, whatever the font asks for.
|
||||
/// </summary>
|
||||
private Button SmallButton(MaterialIconKind icon, string tip, Action onClick)
|
||||
{
|
||||
var btn = SmallButton(tip, onClick);
|
||||
btn.Content = new MaterialIcon { Kind = icon, Width = 16, Height = 16 };
|
||||
return btn;
|
||||
}
|
||||
|
||||
private Button SmallButton(string glyph, string tip, Action onClick)
|
||||
private Button SmallButton(string tip, Action onClick)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Content = glyph + TextPresentation,
|
||||
FontSize = 15,
|
||||
FontSize = 13,
|
||||
Padding = new Thickness(0),
|
||||
MinWidth = 0,
|
||||
MinHeight = 0,
|
||||
Width = 26,
|
||||
Width = 28,
|
||||
Height = 22,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
@@ -378,9 +538,26 @@ public sealed class VehicleCards
|
||||
return btn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orientation is a state, not just an action, so the button carries it: lit means
|
||||
/// this vehicle sits reversed. Reading it off the card beats a checkbox in a panel
|
||||
/// two columns away, and the card is rebuilt on every flip so the paint is current.
|
||||
/// </summary>
|
||||
private Button FlipButton(ConsistItem item)
|
||||
{
|
||||
var btn = SmallButton(MaterialIconKind.SwapHorizontal, App.Loc["TipFlip"],
|
||||
() => _consist.Flip(item));
|
||||
if (item.Flipped)
|
||||
{
|
||||
btn.Foreground = VehicleCardStyle.Flipped;
|
||||
ToolTip.SetTip(btn, $"{App.Loc["TipFlip"]} \u2014 {App.Loc["Reversed"]}");
|
||||
}
|
||||
return btn;
|
||||
}
|
||||
|
||||
private Button DriverButton(ConsistItem item)
|
||||
{
|
||||
var btn = SmallButton("", App.Loc["TipDriver"], () => _consist.CycleDriver(item));
|
||||
var btn = SmallButton(App.Loc["TipDriver"], () => _consist.CycleDriver(item));
|
||||
btn.FontWeight = FontWeight.Bold;
|
||||
_driverChrome.Add((btn, item));
|
||||
PaintDriverButton(btn, item);
|
||||
|
||||
@@ -10,16 +10,17 @@ using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Controls;
|
||||
using StarterNG.Domain;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
public sealed class VehicleDetails
|
||||
{
|
||||
private readonly HeaderedContentControl selectedVehiclePanel;
|
||||
private readonly StackPanel generalPanel;
|
||||
private readonly StackPanel consistTexturePanel;
|
||||
private readonly StackPanel couplerPanel;
|
||||
private readonly Panel couplerActions;
|
||||
private readonly StackPanel brakesPanel;
|
||||
private readonly StackPanel loadsPanel;
|
||||
private readonly StackPanel damagePanel;
|
||||
@@ -36,17 +37,17 @@ public sealed class VehicleDetails
|
||||
private readonly Action<VehicleTexture, StackPanel> _showTextureInfo;
|
||||
|
||||
public VehicleDetails(
|
||||
HeaderedContentControl selectedVehiclePanel,
|
||||
StackPanel generalPanel, StackPanel consistTexturePanel, StackPanel couplerPanel,
|
||||
Panel couplerActions,
|
||||
StackPanel brakesPanel, StackPanel loadsPanel, StackPanel damagePanel,
|
||||
Button removeVehicleButton,
|
||||
VehicleDatabase db, VehicleInfo info, Consist consist, Cargo cargo, Cursor hand,
|
||||
Action redraw, Action refreshChrome, Action<VehicleTexture, StackPanel> showTextureInfo)
|
||||
{
|
||||
this.selectedVehiclePanel = selectedVehiclePanel;
|
||||
this.generalPanel = generalPanel;
|
||||
this.consistTexturePanel = consistTexturePanel;
|
||||
this.couplerPanel = couplerPanel;
|
||||
this.couplerActions = couplerActions;
|
||||
this.brakesPanel = brakesPanel;
|
||||
this.loadsPanel = loadsPanel;
|
||||
this.damagePanel = damagePanel;
|
||||
@@ -67,31 +68,27 @@ public sealed class VehicleDetails
|
||||
generalPanel.Children.Clear();
|
||||
consistTexturePanel.Children.Clear();
|
||||
couplerPanel.Children.Clear();
|
||||
couplerActions.Children.Clear();
|
||||
brakesPanel.Children.Clear();
|
||||
loadsPanel.Children.Clear();
|
||||
damagePanel.Children.Clear();
|
||||
|
||||
selectedVehiclePanel.Header = _consist.Selected is null
|
||||
? App.Loc["SelectedVehicle"]
|
||||
: $"{App.Loc["SelectedVehicle"]}: " +
|
||||
(_consist.Selected.Grouped ? Consist.UnitLabel(_consist.Selected) : _consist.Selected.Cars[0].Name);
|
||||
|
||||
if (_consist.Selected != null && !_consist.Selected.Cars.Contains(_consist.SelectedCar!))
|
||||
_consist.SelectedCar = _consist.Selected.Cars[0];
|
||||
|
||||
_refreshChrome();
|
||||
|
||||
loadsPanel.Children.Add(BuildConsistLoadTools());
|
||||
|
||||
if (_consist.Selected is null)
|
||||
{
|
||||
generalPanel.Children.Add(DetailHint(App.Loc["SelectVehicleHint"]));
|
||||
consistTexturePanel.Children.Add(DetailHint(App.Loc["SelectVehicleHint"]));
|
||||
couplerPanel.Children.Add(DetailHint(App.Loc["SelectVehicleHint"]));
|
||||
brakesPanel.Children.Add(DetailHint(App.Loc["SelectVehicleHint"]));
|
||||
loadsPanel.Children.Add(DetailHint(App.Loc["SelectVehicleHint"]));
|
||||
damagePanel.Children.Add(DetailHint(App.Loc["SelectVehicleHint"]));
|
||||
generalPanel.Children.Add(PanelNote(App.Loc["SelectVehicleHint"]));
|
||||
consistTexturePanel.Children.Add(PanelNote(App.Loc["SelectVehicleHint"]));
|
||||
couplerPanel.Children.Add(PanelNote(App.Loc["SelectVehicleHint"]));
|
||||
brakesPanel.Children.Add(PanelNote(App.Loc["SelectVehicleHint"]));
|
||||
loadsPanel.Children.Add(PanelNote(App.Loc["SelectVehicleHint"]));
|
||||
damagePanel.Children.Add(PanelNote(App.Loc["SelectVehicleHint"]));
|
||||
removeVehicleButton.IsEnabled = false;
|
||||
// The whole-consist tools still apply with nothing selected.
|
||||
AddConsistLoadSection();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,21 +97,27 @@ public sealed class VehicleDetails
|
||||
if (_db.TextureForSkin(_consist.ActiveCar(_consist.Selected).SkinFile) is { } tex)
|
||||
_showTextureInfo(tex, consistTexturePanel);
|
||||
else
|
||||
consistTexturePanel.Children.Add(DetailHint(App.Loc["NoTextureInfo"]));
|
||||
consistTexturePanel.Children.Add(PanelNote(App.Loc["NoTextureInfo"]));
|
||||
|
||||
removeVehicleButton.IsEnabled = true;
|
||||
|
||||
if (_consist.Selected.Cars.Count > 1)
|
||||
{
|
||||
couplerPanel.Children.Add(UnitScopeHint(App.Loc["CouplingUnitRear"]));
|
||||
brakesPanel.Children.Add(UnitScopeHint(App.Loc["AppliesToUnit"]));
|
||||
couplerPanel.Children.Add(PanelNote(App.Loc["CouplingUnitRear"]));
|
||||
brakesPanel.Children.Add(PanelNote(App.Loc["AppliesToUnit"]));
|
||||
}
|
||||
|
||||
BuildCouplerEditor(_consist.Selected);
|
||||
BuildBrakesEditor(_consist.Selected);
|
||||
BuildLoadsEditor(_consist.Selected);
|
||||
BuildConfigExtras(_consist.Selected);
|
||||
BuildDamageEditor(_consist.Selected);
|
||||
|
||||
// Configuration tab: what this vehicle is, what it carries, then the actions
|
||||
// that reach past it - each under a header that says which is which.
|
||||
loadsPanel.Children.Add(
|
||||
Section(App.Loc["Vehicle"], BuildVehicleSection(_consist.Selected)));
|
||||
loadsPanel.Children.Add(
|
||||
Section(App.Loc["Load"], BuildLoadSection(_consist.Selected)));
|
||||
AddConsistLoadSection();
|
||||
}
|
||||
|
||||
private void BuildGeneralInfo(ConsistItem item)
|
||||
@@ -122,6 +125,9 @@ public sealed class VehicleDetails
|
||||
var car = _consist.ActiveCar(item);
|
||||
var inv = CultureInfo.InvariantCulture;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(car.Name))
|
||||
generalPanel.Children.Add(Reading(App.Loc["Name"], car.Name));
|
||||
|
||||
bool unit = item.Cars.Count > 1;
|
||||
var members = item.Cars.Select(_info.PhysicsFor).Where(p => p != null).ToList();
|
||||
|
||||
@@ -132,49 +138,19 @@ public sealed class VehicleDetails
|
||||
double? vmax = members.Count == 0 ? null
|
||||
: unit ? members.Min(p => p!.VMax) : members[0]!.VMax;
|
||||
|
||||
generalPanel.Children.Add(InfoRow($"{App.Loc["Length"]} [m]:",
|
||||
generalPanel.Children.Add(Reading($"{App.Loc["Length"]} [m]",
|
||||
length?.ToString("0.##", inv)));
|
||||
generalPanel.Children.Add(InfoRow($"{App.Loc["Mass"]} [t]:",
|
||||
generalPanel.Children.Add(Reading($"{App.Loc["Mass"]} [t]",
|
||||
mass?.ToString("0.#", inv)));
|
||||
generalPanel.Children.Add(InfoRow($"{App.Loc["VMax"]} [km/h]:",
|
||||
generalPanel.Children.Add(Reading($"{App.Loc["VMax"]} [km/h]",
|
||||
vmax?.ToString("0", inv)));
|
||||
|
||||
if (unit)
|
||||
generalPanel.Children.Add(InfoRow($"{App.Loc["VehicleCount"]}:",
|
||||
generalPanel.Children.Add(Reading(App.Loc["VehicleCount"],
|
||||
item.Cars.Count.ToString(inv)));
|
||||
|
||||
generalPanel.Children.Add(InfoRow($"{App.Loc["Model"]}:", car.MmdFile));
|
||||
generalPanel.Children.Add(InfoRow($"{App.Loc["Texture"]}:", car.SkinFile));
|
||||
}
|
||||
|
||||
private static Control InfoRow(string label, string? value)
|
||||
{
|
||||
var grid = new Grid { ColumnDefinitions = new ColumnDefinitions("Auto,10,*") };
|
||||
|
||||
var caption = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 12,
|
||||
MinWidth = 110,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
var reading = new TextBlock
|
||||
{
|
||||
Text = string.IsNullOrWhiteSpace(value) ? "–" : value,
|
||||
FontSize = 12,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
|
||||
Grid.SetColumn(reading, 2);
|
||||
grid.Children.Add(caption);
|
||||
grid.Children.Add(reading);
|
||||
return grid;
|
||||
generalPanel.Children.Add(Reading(App.Loc["Model"], car.MmdFile));
|
||||
generalPanel.Children.Add(Reading(App.Loc["Texture"], car.SkinFile));
|
||||
}
|
||||
|
||||
private void BuildCouplerEditor(ConsistItem item)
|
||||
@@ -187,12 +163,17 @@ public sealed class VehicleDetails
|
||||
|
||||
var d = Consist.TailCar(item);
|
||||
|
||||
var grid = new Grid
|
||||
// Two columns: the tab is wide enough and the list is short enough
|
||||
// to avoid scrolling while still reading left-to-right, top-to-bottom.
|
||||
var list = new Grid
|
||||
{
|
||||
ColumnDefinitions = new ColumnDefinitions("*,8,*"),
|
||||
RowDefinitions = new RowDefinitions("Auto,Auto,Auto,Auto"),
|
||||
RowSpacing = 4
|
||||
ColumnDefinitions = new ColumnDefinitions("*,*"),
|
||||
RowSpacing = 2,
|
||||
ColumnSpacing = 16
|
||||
};
|
||||
for (int r = 0; r < (CouplingBits.BitKeys.Length + 1) / 2; r++)
|
||||
list.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
||||
|
||||
for (int i = 0; i < CouplingBits.BitKeys.Length; i++)
|
||||
{
|
||||
int bit = 1 << i;
|
||||
@@ -207,17 +188,16 @@ public sealed class VehicleDetails
|
||||
d.Coupling.Set(bit, check.IsChecked == true);
|
||||
_redraw();
|
||||
};
|
||||
int half = (CouplingBits.BitKeys.Length + 1) / 2;
|
||||
Grid.SetColumn(check, i < half ? 0 : 2);
|
||||
Grid.SetRow(check, i % half);
|
||||
grid.Children.Add(check);
|
||||
Grid.SetRow(check, i / 2);
|
||||
Grid.SetColumn(check, i % 2);
|
||||
list.Children.Add(check);
|
||||
}
|
||||
couplerPanel.Children.Add(grid);
|
||||
couplerPanel.Children.Add(list);
|
||||
|
||||
var copy = new Button
|
||||
{
|
||||
Content = App.Loc["CopyCoupler"],
|
||||
FontSize = 11,
|
||||
FontSize = 12,
|
||||
Cursor = _hand,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||
@@ -235,7 +215,7 @@ public sealed class VehicleDetails
|
||||
var auto = new Button
|
||||
{
|
||||
Content = App.Loc["AutoCoupler"],
|
||||
FontSize = 11,
|
||||
FontSize = 12,
|
||||
Cursor = _hand,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||
@@ -250,19 +230,23 @@ public sealed class VehicleDetails
|
||||
Refresh();
|
||||
};
|
||||
|
||||
var buttons = new Grid
|
||||
// Laid out horizontally under the checkbox list so the grid can use the full tab width
|
||||
var buttons = new StackPanel
|
||||
{
|
||||
ColumnDefinitions = new ColumnDefinitions("125*,4,48*"),
|
||||
Orientation = Orientation.Horizontal,
|
||||
Spacing = 4,
|
||||
Margin = new Thickness(0, 6, 0, 0)
|
||||
};
|
||||
Grid.SetColumn(copy, 0);
|
||||
Grid.SetColumn(auto, 2);
|
||||
buttons.Children.Add(copy);
|
||||
buttons.Children.Add(auto);
|
||||
couplerPanel.Children.Add(buttons);
|
||||
couplerActions.Children.Add(buttons);
|
||||
}
|
||||
|
||||
private void BuildConfigExtras(ConsistItem item)
|
||||
/// <summary>
|
||||
/// The vehicle's own settings. Every write path is unchanged: crew and wagon number
|
||||
/// go through the unit's active car, the coolant flag to its first car only.
|
||||
/// </summary>
|
||||
private Control BuildVehicleSection(ConsistItem item)
|
||||
{
|
||||
var driver = new ComboBox { FontSize = 12, MinWidth = 0 };
|
||||
foreach (string key in new[] { "DriverHead", "DriverRear", "DriverPassenger", "DriverNobody" })
|
||||
@@ -296,75 +280,143 @@ public sealed class VehicleDetails
|
||||
};
|
||||
driver.MinWidth = 0;
|
||||
driver.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
loadsPanel.Children.Insert(0, LabeledRow(App.Loc["CrewLabel"], driver, labelWidth: 70));
|
||||
|
||||
var reversed = new CheckBox
|
||||
{
|
||||
Content = App.Loc["Reversed"],
|
||||
IsChecked = item.Flipped,
|
||||
FontSize = 11
|
||||
};
|
||||
reversed.IsCheckedChanged += (_, _) =>
|
||||
{
|
||||
if ((reversed.IsChecked == true) != item.Flipped)
|
||||
_consist.Flip(item);
|
||||
};
|
||||
var rows = new StackPanel();
|
||||
rows.Children.Add(Row(App.Loc["CrewLabel"], driver));
|
||||
|
||||
if (IsPassengerVehicle(item))
|
||||
{
|
||||
var lead = item.Cars[0];
|
||||
var numBox = new NumericUpDown
|
||||
{
|
||||
FontSize = 12,
|
||||
Minimum = 0,
|
||||
Maximum = 99999,
|
||||
Increment = 1,
|
||||
Value = Consist.GetWagonNumber(lead),
|
||||
FormatString = "0"
|
||||
};
|
||||
numBox.ValueChanged += (_, _) =>
|
||||
{
|
||||
_consist.SetWagonNumber(lead, (int)(numBox.Value ?? 0));
|
||||
_redraw();
|
||||
};
|
||||
rows.Children.Add(Row(App.Loc["WagonNumber"], numBox));
|
||||
}
|
||||
|
||||
// Orientation is not here any more: it reads and toggles off the flip button on
|
||||
// the vehicle's own card, which lights up when the vehicle sits reversed.
|
||||
// The flag rides on the unit's first car, so that is the car that has to own a
|
||||
// diesel engine for it to mean anything: the simulator runs its heat model only
|
||||
// for DieselEngine and DieselElectric, and on anything else ".TA" is written into
|
||||
// the scenario and then ignored. Shown anyway when the flag is already set, so an
|
||||
// inherited one stays visible and removable.
|
||||
var d = item.Cars[0];
|
||||
var thermo = new CheckBox
|
||||
{
|
||||
Content = App.Loc["ThermoAmbient"],
|
||||
IsChecked = d.Coupling.ThermoDynamic,
|
||||
FontSize = 11
|
||||
};
|
||||
thermo.IsCheckedChanged += (_, _) => d.Coupling.ThermoDynamic = thermo.IsChecked == true;
|
||||
bool thermoApplies = _info.PhysicsFor(d)?.IsDieselEngine == true
|
||||
|| d.Coupling.ThermoDynamic;
|
||||
|
||||
var switches = new Grid
|
||||
if (thermoApplies)
|
||||
{
|
||||
ColumnDefinitions = new ColumnDefinitions("*,8,*"),
|
||||
Margin = new Thickness(0, 4, 0, 0)
|
||||
};
|
||||
Grid.SetColumn(thermo, 2);
|
||||
switches.Children.Add(reversed);
|
||||
switches.Children.Add(thermo);
|
||||
loadsPanel.Children.Add(switches);
|
||||
var thermo = new CheckBox
|
||||
{
|
||||
Content = App.Loc["ThermoAmbient"],
|
||||
IsChecked = d.Coupling.ThermoDynamic,
|
||||
FontSize = 12
|
||||
};
|
||||
thermo.IsCheckedChanged += (_, _) => d.Coupling.ThermoDynamic = thermo.IsChecked == true;
|
||||
|
||||
// Label-less row, so the box indents to the control column with everything
|
||||
// else instead of starting at the panel's left edge on its own.
|
||||
rows.Children.Add(Row(content: thermo));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static TextBlock UnitScopeHint(string text) => new()
|
||||
private bool IsPassengerVehicle(ConsistItem item)
|
||||
{
|
||||
var lead = _consist.ActiveCar(item);
|
||||
var phys = _info.PhysicsFor(lead);
|
||||
if (phys == null)
|
||||
return false;
|
||||
return phys.LoadAccepted.Split(',', ';')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => s.Length > 0)
|
||||
.Any(s => s.Equals("passengers", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The one row shape every detail tab uses. The label column negotiates its width
|
||||
/// through the shared-size group on the host panel, so all rows in a panel line up
|
||||
/// without anyone naming a pixel count - see the Compact styles in Settings.axaml.
|
||||
/// Omitting <paramref name="content"/> makes it a read-only line; omitting
|
||||
/// <paramref name="label"/> indents a bare widget to the control column.
|
||||
/// </summary>
|
||||
private static SettingRow Row(string? label = null, Control? content = null,
|
||||
string? value = null, string? description = null,
|
||||
string? tip = null)
|
||||
{
|
||||
var row = new SettingRow
|
||||
{
|
||||
Label = label,
|
||||
Description = description,
|
||||
ValueText = value,
|
||||
Content = content
|
||||
};
|
||||
row.Classes.Add("Compact");
|
||||
if (!string.IsNullOrWhiteSpace(tip))
|
||||
ToolTip.SetTip(row, tip);
|
||||
return row;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A read-only label/value line. The dash keeps the row from collapsing when a
|
||||
/// vehicle has no reading for the field.
|
||||
/// </summary>
|
||||
private static SettingRow Reading(string label, string? value)
|
||||
{
|
||||
bool empty = string.IsNullOrWhiteSpace(value);
|
||||
return Row(label, value: empty ? "\u2013" : value, tip: empty ? null : value);
|
||||
}
|
||||
|
||||
private static SettingsSection Section(string header, Control content, Control? meta = null)
|
||||
{
|
||||
var section = new SettingsSection
|
||||
{
|
||||
Header = header,
|
||||
Content = content,
|
||||
// Passive text only: Toggle sits outside the logical tree, so nothing put
|
||||
// here can be found later with GetLogicalDescendants.
|
||||
Toggle = meta
|
||||
};
|
||||
section.Classes.Add("Compact");
|
||||
return section;
|
||||
}
|
||||
|
||||
private static StackPanel Stack(params Control[] children)
|
||||
{
|
||||
var panel = new StackPanel { Spacing = 3 };
|
||||
foreach (var c in children)
|
||||
panel.Children.Add(c);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private static readonly IBrush DimBrush = new SolidColorBrush(Color.Parse("#9098A0"));
|
||||
|
||||
/// <summary>
|
||||
/// A note that governs a whole panel or section rather than one row - same look as a
|
||||
/// row's description line. Replaces the old DetailHint/UnitScopeHint pair, which
|
||||
/// differed only by a 2 px margin.
|
||||
/// </summary>
|
||||
private static TextBlock PanelNote(string text) => new()
|
||||
{
|
||||
Text = text,
|
||||
FontSize = 11,
|
||||
Opacity = 0.6,
|
||||
FontSize = 12,
|
||||
Foreground = DimBrush,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 0, 0, 2)
|
||||
Margin = new Thickness(0, 0, 0, 8)
|
||||
};
|
||||
|
||||
private static TextBlock DetailHint(string text) => new()
|
||||
{
|
||||
Text = text, Opacity = 0.6, FontSize = 12, TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
|
||||
private static Control LabeledRow(string label, Control control, double labelWidth = 120)
|
||||
{
|
||||
var grid = new Grid { ColumnDefinitions = new ColumnDefinitions("Auto,8,*") };
|
||||
|
||||
var caption = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 12,
|
||||
MinWidth = labelWidth,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
control.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
Grid.SetColumn(control, 2);
|
||||
grid.Children.Add(caption);
|
||||
grid.Children.Add(control);
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static void AddOption(ComboBox combo, string content, string? tag)
|
||||
=> combo.Items.Add(new ComboBoxItem { Content = content, Tag = tag });
|
||||
|
||||
@@ -402,9 +454,9 @@ public sealed class VehicleDetails
|
||||
switchCombo.SelectionChanged += (_, _) => Apply();
|
||||
loadCombo.SelectionChanged += (_, _) => Apply();
|
||||
|
||||
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeMode"], modeCombo, labelWidth: 96));
|
||||
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeSwitch"], switchCombo, labelWidth: 96));
|
||||
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeLoad"], loadCombo, labelWidth: 96));
|
||||
brakesPanel.Children.Add(Row(App.Loc["BrakeMode"], modeCombo));
|
||||
brakesPanel.Children.Add(Row(App.Loc["BrakeSwitch"], switchCombo));
|
||||
brakesPanel.Children.Add(Row(App.Loc["BrakeLoad"], loadCombo));
|
||||
}
|
||||
|
||||
private static string BrakeModeLabel(string? code) => code switch
|
||||
@@ -413,7 +465,6 @@ public sealed class VehicleDetails
|
||||
"P" => App.Loc["BrakePassenger"],
|
||||
"R" => App.Loc["BrakeExpress"],
|
||||
"M" => App.Loc["BrakeExpressMg"],
|
||||
"Q" => App.Loc["BrakeNoAir"],
|
||||
_ => App.Loc["BrakeActingNone"]
|
||||
};
|
||||
|
||||
@@ -427,8 +478,12 @@ public sealed class VehicleDetails
|
||||
|
||||
private static string SwitchLabel(string? code) => code switch
|
||||
{
|
||||
"<>" => App.Loc["SwitchEmergencyTest"],
|
||||
"0" => App.Loc["SwitchOff"],
|
||||
"1" => App.Loc["SwitchOff10"],
|
||||
"X" => App.Loc["SwitchAgonal"],
|
||||
"E" => App.Loc["SwitchEmptied"],
|
||||
"Q" => App.Loc["BrakeNoAir"],
|
||||
_ => App.Loc["SwitchNormal"]
|
||||
};
|
||||
|
||||
@@ -439,7 +494,7 @@ public sealed class VehicleDetails
|
||||
NumericUpDown Spin(int value, int max) => new()
|
||||
{
|
||||
FontSize = 12, Minimum = 0, Maximum = max, Increment = 1,
|
||||
Value = value, MinWidth = 0, Width = 100, FormatString = "0"
|
||||
Value = value, MinWidth = 0, FormatString = "0"
|
||||
};
|
||||
|
||||
var sway = Spin(w.Sway, 100);
|
||||
@@ -465,84 +520,100 @@ public sealed class VehicleDetails
|
||||
flatRand.ValueChanged += (_, _) => Apply();
|
||||
flatProb.ValueChanged += (_, _) => Apply();
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
ColumnDefinitions = new ColumnDefinitions("*,8,Auto"),
|
||||
RowSpacing = 6
|
||||
};
|
||||
void Row(string label, Control control)
|
||||
{
|
||||
int r = grid.RowDefinitions.Count;
|
||||
grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
||||
|
||||
var caption = new TextBlock
|
||||
{
|
||||
Text = label, FontSize = 12,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
Grid.SetRow(caption, r);
|
||||
Grid.SetColumn(caption, 0);
|
||||
|
||||
control.HorizontalAlignment = HorizontalAlignment.Right;
|
||||
Grid.SetRow(control, r);
|
||||
Grid.SetColumn(control, 2);
|
||||
|
||||
grid.Children.Add(caption);
|
||||
grid.Children.Add(control);
|
||||
}
|
||||
|
||||
Row(App.Loc["DamageSway"], sway);
|
||||
Row(App.Loc["DamageFlatness"], flat);
|
||||
Row(App.Loc["DamageFlatnessRand"], flatRand);
|
||||
Row(App.Loc["DamageFlatnessProb"], flatProb);
|
||||
damagePanel.Children.Add(grid);
|
||||
damagePanel.Children.Add(Row(App.Loc["DamageSway"], sway));
|
||||
damagePanel.Children.Add(Row(App.Loc["DamageFlatness"], flat));
|
||||
damagePanel.Children.Add(Row(App.Loc["DamageFlatnessRand"], flatRand));
|
||||
damagePanel.Children.Add(Row(App.Loc["DamageFlatnessProb"], flatProb));
|
||||
}
|
||||
|
||||
private Control BuildConsistLoadTools()
|
||||
/// <summary>
|
||||
/// The three actions that fill the selected vehicle from its neighbours. They keep
|
||||
/// the primary (filled) look because they act on what you have selected.
|
||||
/// </summary>
|
||||
private Control BuildVehicleLoadTools(ConsistItem sel)
|
||||
{
|
||||
var panel = new StackPanel { Spacing = 4, Margin = new Thickness(0, 0, 0, 4) };
|
||||
var row = new WrapPanel { Orientation = Orientation.Horizontal };
|
||||
row.Children.Add(LoadToolButton(App.Loc["ConsistRandomType"],
|
||||
() => { _cargo.RandomTypes(_consist); _redraw(); }));
|
||||
row.Children.Add(LoadToolButton(App.Loc["ConsistMaxAmount"],
|
||||
() => { _cargo.MaxAmounts(_consist); _redraw(); }));
|
||||
row.Children.Add(LoadToolButton(App.Loc["ConsistRandomAmount"],
|
||||
() => { _cargo.RandomAmounts(_consist); _redraw(); }));
|
||||
panel.Children.Add(row);
|
||||
return panel;
|
||||
bool loadable = _cargo.IsLoadable(sel);
|
||||
|
||||
var prev = LoadToolButton(App.Loc["LoadPrevShort"],
|
||||
() => { if (_consist.Selected is { } s) { _cargo.CopyFromPrevious(_consist, s); _redraw(); } });
|
||||
prev.IsEnabled = loadable && _consist.IndexOf(sel) > 0;
|
||||
ToolTip.SetTip(prev, App.Loc["LoadCopyPrev"]);
|
||||
|
||||
var max = LoadToolButton(App.Loc["LoadMaxShort"],
|
||||
() => { if (_consist.Selected is { } s) { _cargo.FillUnit(s); _redraw(); } });
|
||||
max.IsEnabled = loadable;
|
||||
ToolTip.SetTip(max, App.Loc["LoadMax"]);
|
||||
|
||||
var toAll = LoadToolButton(App.Loc["LoadToAllShort"],
|
||||
() => { if (_consist.Selected is { } s) { _cargo.CopyToFollowing(_consist, s); _redraw(); } });
|
||||
toAll.IsEnabled = loadable;
|
||||
ToolTip.SetTip(toAll, App.Loc["LoadCopyToAll"]);
|
||||
|
||||
return Stack(prev, max, toAll);
|
||||
}
|
||||
|
||||
private Button LoadToolButton(string text, Action onClick)
|
||||
/// <summary>
|
||||
/// The whole-consist actions. Secondary styling plus a header that names the scope
|
||||
/// and counts the vehicles, because these reach far past the selected card. No
|
||||
/// tooltips: theirs used to repeat the button label word for word.
|
||||
/// </summary>
|
||||
private void AddConsistLoadSection()
|
||||
{
|
||||
if (_consist.Count == 0)
|
||||
return;
|
||||
|
||||
var buttons = Stack(
|
||||
LoadToolButton(App.Loc["ConsistRandomType"],
|
||||
() => { _cargo.RandomTypes(_consist); _redraw(); }, secondary: true),
|
||||
LoadToolButton(App.Loc["ConsistMaxAmount"],
|
||||
() => { _cargo.MaxAmounts(_consist); _redraw(); }, secondary: true),
|
||||
LoadToolButton(App.Loc["ConsistRandomAmount"],
|
||||
() => { _cargo.RandomAmounts(_consist); _redraw(); }, secondary: true));
|
||||
|
||||
var count = new TextBlock
|
||||
{
|
||||
Text = $"{App.Loc["VehicleCount"]}: {_consist.Count}",
|
||||
FontSize = 12,
|
||||
Foreground = DimBrush,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
loadsPanel.Children.Add(Section(App.Loc["ConsistLoadTools"], buttons, count));
|
||||
}
|
||||
|
||||
private Button LoadToolButton(string text, Action onClick, bool secondary = false)
|
||||
{
|
||||
var b = new Button
|
||||
{
|
||||
Content = text, FontSize = 11, Margin = new Thickness(0, 0, 4, 4),
|
||||
Padding = new Thickness(8, 3), Cursor = _hand,
|
||||
Content = text, FontSize = 12,
|
||||
Padding = new Thickness(6, 2), Cursor = _hand,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||
IsEnabled = _consist.Count > 0
|
||||
};
|
||||
b.Classes.Add("Flat");
|
||||
b.Classes.Add(secondary ? "Outline" : "Flat");
|
||||
b.Click += (_, _) => onClick();
|
||||
return b;
|
||||
}
|
||||
|
||||
private void BuildLoadsEditor(ConsistItem item)
|
||||
/// <summary>
|
||||
/// What the selected vehicle carries, and the actions that fill it. A vehicle that
|
||||
/// takes no cargo gets the note alone - previously the three actions stayed behind,
|
||||
/// greyed out and useless.
|
||||
/// </summary>
|
||||
private Control BuildLoadSection(ConsistItem item)
|
||||
{
|
||||
var lead = _consist.ActiveCar(item);
|
||||
var available = _cargo.AcceptedTypes(lead);
|
||||
|
||||
if (available.Count == 0 && string.IsNullOrEmpty(lead.LoadType))
|
||||
{
|
||||
loadsPanel.Children.Add(DetailHint(App.Loc["LoadNotLoadable"]));
|
||||
return;
|
||||
}
|
||||
return PanelNote(App.Loc["LoadNotLoadable"]);
|
||||
|
||||
if (!string.IsNullOrEmpty(lead.LoadType) &&
|
||||
!available.Contains(lead.LoadType!, StringComparer.OrdinalIgnoreCase))
|
||||
available.Insert(0, lead.LoadType!);
|
||||
|
||||
var typeCombo = new ComboBox { FontSize = 12, MinWidth = 0 };
|
||||
var typeCombo = new ComboBox { FontSize = 12, MinWidth = 120 };
|
||||
typeCombo.Items.Add(new ComboBoxItem { Content = App.Loc["None"], Tag = null });
|
||||
foreach (var name in available)
|
||||
typeCombo.Items.Add(new ComboBoxItem { Content = LoadWeights.Describe(name), Tag = name });
|
||||
@@ -557,41 +628,129 @@ public sealed class VehicleDetails
|
||||
break;
|
||||
}
|
||||
|
||||
string? SelectedType() => (typeCombo.SelectedItem as ComboBoxItem)?.Tag as string;
|
||||
bool IsPant() => Dynamic.IsPantStateType(SelectedType());
|
||||
|
||||
int limit = _cargo.LimitFor(lead);
|
||||
var countBox = new NumericUpDown
|
||||
{
|
||||
FontSize = 12, Minimum = 0, Maximum = limit, Increment = 1,
|
||||
Value = Math.Min(lead.LoadCount, limit), MinWidth = 120, FormatString = "0"
|
||||
Value = Math.Min(lead.LoadCount, limit), FormatString = "0",
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
|
||||
var pantHint = DetailHint(App.Loc["LoadPantStateHint"]);
|
||||
pantHint.IsVisible = lead.IsPantState;
|
||||
var pantStateCombo = new ComboBox
|
||||
{
|
||||
FontSize = 12, MinWidth = 0,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
pantStateCombo.Items.Add(new ComboBoxItem { Content = App.Loc["PantStateNone"], Tag = 0 });
|
||||
pantStateCombo.Items.Add(new ComboBoxItem { Content = "A", Tag = 1 });
|
||||
pantStateCombo.Items.Add(new ComboBoxItem { Content = "B", Tag = 2 });
|
||||
pantStateCombo.Items.Add(new ComboBoxItem { Content = App.Loc["PantStateBoth"], Tag = 3 });
|
||||
|
||||
string? SelectedType() => (typeCombo.SelectedItem as ComboBoxItem)?.Tag as string;
|
||||
var countContainer = new Panel();
|
||||
countContainer.Children.Add(countBox);
|
||||
countContainer.Children.Add(pantStateCombo);
|
||||
|
||||
string UnitText()
|
||||
{
|
||||
var phys = _info.PhysicsFor(lead);
|
||||
if (phys == null) return App.Loc["LoadUnitGeneric"];
|
||||
if (string.Equals(phys.LoadQ, "pieces", StringComparison.OrdinalIgnoreCase))
|
||||
return App.Loc["LoadUnitPieces"];
|
||||
if (string.Equals(phys.LoadQ, "tonns", StringComparison.OrdinalIgnoreCase))
|
||||
return App.Loc["LoadUnitTons"];
|
||||
return App.Loc["LoadUnitGeneric"];
|
||||
}
|
||||
|
||||
var countRow = Row(App.Loc["LoadCount"], countContainer);
|
||||
|
||||
void SyncPantStateFromCount()
|
||||
{
|
||||
int value = Math.Clamp((int)(countBox.Value ?? 0), 0, Dynamic.PantStateMax);
|
||||
foreach (var obj in pantStateCombo.Items)
|
||||
if (obj is ComboBoxItem ci && ci.Tag is int t && t == value)
|
||||
{
|
||||
pantStateCombo.SelectedItem = ci;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SyncCountFromPantState()
|
||||
{
|
||||
int value = pantStateCombo.SelectedItem is ComboBoxItem { Tag: int t } ? t : 0;
|
||||
countBox.Value = Math.Clamp(value, 0, countBox.Maximum);
|
||||
}
|
||||
|
||||
void UpdateVisibility()
|
||||
{
|
||||
bool pant = IsPant();
|
||||
countBox.IsVisible = !pant;
|
||||
pantStateCombo.IsVisible = pant;
|
||||
// The unit slot is reserved by the Compact style even when empty, so the
|
||||
// spinner does not jump sideways as the unit comes and goes.
|
||||
countRow.Label = pant ? App.Loc["Pantograph"] : App.Loc["LoadCount"];
|
||||
countRow.ValueText = pant ? null : UnitText();
|
||||
// The "set an amount above 0" advice is only true while the amount is 0;
|
||||
// past that it is a line of noise in a panel with no height to spare.
|
||||
countRow.Description = pant ? App.Loc["LoadPantStateHint"]
|
||||
: GetCount() == 0 ? App.Loc["LoadHint"]
|
||||
: null;
|
||||
}
|
||||
|
||||
int GetCount()
|
||||
{
|
||||
if (IsPant())
|
||||
return pantStateCombo.SelectedItem is ComboBoxItem { Tag: int t } ? t : 0;
|
||||
return (int)(countBox.Value ?? 0);
|
||||
}
|
||||
|
||||
void Apply()
|
||||
{
|
||||
string? type = SelectedType();
|
||||
int count = (int)(countBox.Value ?? 0);
|
||||
int count = GetCount();
|
||||
foreach (var c in _consist.MemberTargets(item))
|
||||
{
|
||||
|
||||
c.LoadType = string.IsNullOrEmpty(type) ? null : type;
|
||||
c.LoadCount = string.IsNullOrEmpty(type) ? 0 : Math.Min(count, _cargo.LimitFor(c, type));
|
||||
}
|
||||
}
|
||||
|
||||
// Seed the pantograph combo from the model before any handler is listening.
|
||||
SyncPantStateFromCount();
|
||||
bool wasPant = IsPant();
|
||||
|
||||
typeCombo.SelectionChanged += (_, _) =>
|
||||
{
|
||||
string? type = SelectedType();
|
||||
pantHint.IsVisible = Dynamic.IsPantStateType(type);
|
||||
countBox.Maximum = _cargo.LimitFor(lead, type);
|
||||
countBox.Maximum = _cargo.LimitFor(lead, SelectedType());
|
||||
|
||||
// Both widgets edit the same LoadCount, so the value is carried across only
|
||||
// when the mode actually flips. Doing it unconditionally - as UpdateVisibility
|
||||
// used to - reset the amount to zero on every rebuild, which is why setting
|
||||
// the maximum load appeared to do nothing: the model was written, the panel
|
||||
// refreshed, and the refresh immediately wrote zero back.
|
||||
bool pant = IsPant();
|
||||
if (pant != wasPant)
|
||||
{
|
||||
if (pant)
|
||||
SyncPantStateFromCount();
|
||||
else
|
||||
SyncCountFromPantState();
|
||||
wasPant = pant;
|
||||
}
|
||||
|
||||
UpdateVisibility();
|
||||
Apply();
|
||||
};
|
||||
countBox.ValueChanged += (_, _) => Apply();
|
||||
pantStateCombo.SelectionChanged += (_, _) => Apply();
|
||||
countBox.ValueChanged += (_, _) => { Apply(); UpdateVisibility(); };
|
||||
|
||||
loadsPanel.Children.Add(LabeledRow(App.Loc["LoadType"], typeCombo));
|
||||
loadsPanel.Children.Add(LabeledRow(App.Loc["LoadCount"], countBox));
|
||||
loadsPanel.Children.Add(pantHint);
|
||||
UpdateVisibility();
|
||||
|
||||
return Stack(
|
||||
Row(App.Loc["LoadType"], typeCombo),
|
||||
countRow,
|
||||
BuildVehicleLoadTools(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="150" />
|
||||
<!-- Measures itself, so a whole card fits at either thumbnail size. -->
|
||||
<RowDefinition Height="Auto" MinHeight="120" MaxHeight="200" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<HeaderedContentControl Grid.Column="0" Grid.Row="0" Theme="{StaticResource GroupBox}"
|
||||
@@ -23,7 +24,7 @@
|
||||
Header="{Binding [Sceneries], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<CheckBox DockPanel.Dock="Bottom" Name="archivalSwitch"
|
||||
Margin="2,4,0,0" FontSize="11"
|
||||
Margin="2,4,0,0" FontSize="12"
|
||||
IsCheckedChanged="ArchivalSwitch_OnChanged"
|
||||
Content="{Binding [HideArchivalSceneries], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TreeView Name="sceneryList" Classes="Compact"
|
||||
@@ -54,7 +55,7 @@
|
||||
Header="{Binding [SceneryConsists], Source={x:Static starterNg:App.Loc}}">
|
||||
<HeaderedContentControl.Tag>
|
||||
<Button Name="randomTexturesButton" Classes="Flat" Cursor="Hand"
|
||||
FontSize="11" MinHeight="0" Padding="10,1"
|
||||
FontSize="12" MinHeight="0" Padding="10,1"
|
||||
Click="RandomTexturesButton_OnClick"
|
||||
Content="{Binding [RandomizeTextures], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipRandomizeTextures], Source={x:Static starterNg:App.Loc}}" />
|
||||
@@ -144,9 +145,9 @@
|
||||
VerticalAlignment="Center" TextAlignment="Right"
|
||||
FontWeight="SemiBold" />
|
||||
<DockPanel Grid.Row="5" Grid.Column="2" Margin="0,-6,0,0">
|
||||
<TextBlock DockPanel.Dock="Right" FontSize="11"
|
||||
<TextBlock DockPanel.Dock="Right" FontSize="12"
|
||||
Foreground="{StaticResource CarbonFgDim}" Text="+50 °C" />
|
||||
<TextBlock FontSize="11"
|
||||
<TextBlock FontSize="12"
|
||||
Foreground="{StaticResource CarbonFgDim}" Text="-50 °C" />
|
||||
</DockPanel>
|
||||
|
||||
@@ -161,10 +162,10 @@
|
||||
VerticalAlignment="Center" TextAlignment="Right"
|
||||
FontWeight="SemiBold" />
|
||||
<DockPanel Grid.Row="8" Grid.Column="2" Margin="0,-6,0,0">
|
||||
<TextBlock DockPanel.Dock="Right" FontSize="11"
|
||||
<TextBlock DockPanel.Dock="Right" FontSize="12"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [VisibilityGood], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBlock FontSize="11" Foreground="{StaticResource CarbonFgDim}"
|
||||
<TextBlock FontSize="12" Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [VisibilityBad], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
|
||||
@@ -217,9 +218,13 @@
|
||||
|
||||
<HeaderedContentControl Grid.Row="1" Grid.ColumnSpan="3" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||
<!-- Mass, length and the rest ride in the title bar, same as in the
|
||||
depot, instead of taking a row of their own above the cards. -->
|
||||
<HeaderedContentControl.Tag>
|
||||
<StackPanel Name="trainStats" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" />
|
||||
</HeaderedContentControl.Tag>
|
||||
<DockPanel LastChildFill="True">
|
||||
<StackPanel DockPanel.Dock="Top" Name="trainStats" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" Margin="0,0,0,6" />
|
||||
<ScrollViewer Name="consistScroll"
|
||||
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<Panel>
|
||||
|
||||
@@ -896,13 +896,13 @@ public partial class Scenarios : UserControl
|
||||
|
||||
var bmp = LoadMini(miniName, thumbH);
|
||||
Control visual = bmp is not null
|
||||
? new Image
|
||||
? MiniTextures.Sharp(new Image
|
||||
{
|
||||
Source = bmp,
|
||||
Height = thumbH,
|
||||
Stretch = Avalonia.Media.Stretch.Uniform,
|
||||
Margin = new Thickness(0)
|
||||
}
|
||||
})
|
||||
: MissingMiniBox(train.SkinFile, thumbH);
|
||||
|
||||
bool selected = !string.IsNullOrEmpty(train.Name) &&
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,15 +3,19 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Markup.Xaml.Styling;
|
||||
using Avalonia.Media;
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Controls;
|
||||
using StarterNG.Infrastructure;
|
||||
using StarterNG.Services;
|
||||
|
||||
using KeyBinding = StarterNG.Classes.KeyBinding;
|
||||
@@ -22,6 +26,15 @@ public partial class Settings : UserControl
|
||||
{
|
||||
private bool _loading;
|
||||
|
||||
/// <summary>
|
||||
/// Raised once a saved (or applied) setting changes something the other tabs
|
||||
/// have already drawn, so they can redraw instead of waiting for a restart.
|
||||
/// </summary>
|
||||
public event Action? ThumbnailSizeChanged;
|
||||
|
||||
/// <summary>Thumbnail size the other tabs were last drawn at.</summary>
|
||||
private bool _drawnThumbs = StarterNG.Classes.Settings.Instance.LargeThumbnails;
|
||||
|
||||
public Settings()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -41,6 +54,7 @@ public partial class Settings : UserControl
|
||||
|
||||
App.Loc.LanguageChanged += SelectActiveLanguage;
|
||||
App.Loc.LanguageChanged += UpdateQualityCaptions;
|
||||
App.Loc.LanguageChanged += UpdateSaveState;
|
||||
App.Loc.LanguageChanged += () => shaderResolutionSlider_OnValueChanged(null, null);
|
||||
|
||||
FeedbackCb.SelectionChanged += FeedbackCb_OnSelectionChanged;
|
||||
@@ -54,6 +68,10 @@ public partial class Settings : UserControl
|
||||
KeyboardConfig.Instance.Load();
|
||||
BuildControlsTab();
|
||||
AddHandler(KeyDownEvent, OnControlsKeyDown, RoutingStrategies.Tunnel);
|
||||
|
||||
HookDirtyTracking();
|
||||
ApplyFilter();
|
||||
UpdateSaveState();
|
||||
}
|
||||
|
||||
private static int IndexOfTag(ComboBox cb, string? tag)
|
||||
@@ -95,6 +113,7 @@ public partial class Settings : UserControl
|
||||
if (!File.Exists(path)) return;
|
||||
StarterNG.Classes.Settings.Instance.LoadFrom(path);
|
||||
ApplyToUi();
|
||||
RedrawThumbnailsIfNeeded();
|
||||
}
|
||||
|
||||
private void ProfileSaveAsButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
@@ -232,10 +251,9 @@ public partial class Settings : UserControl
|
||||
VehiclesVolumeSlider.Value = s.VehiclesVolume;
|
||||
PositionalVolumeSlider.Value = s.PositionalVolume;
|
||||
AmbientVolumeSlider.Value = s.AmbientVolume;
|
||||
VolumePausedSlider.Value = s.VolumePaused;
|
||||
SkipPipelineCb.IsChecked = s.SkipPipeline;
|
||||
DebugLogVisibleCb.IsChecked = s.DebugLogVisible;
|
||||
PyScreenPriorityCb.SelectedIndex = Math.Max(0, IndexOfTag(PyScreenPriorityCb, s.PyScreenPriority));
|
||||
PyScreenPriorityCb.SelectedIndex = Math.Max(0, IndexOfTag(PyScreenPriorityCb, s.PythonScreenUpdateRate.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
AutoCloseStarterCb.IsChecked = s.AutoCloseStarter;
|
||||
LargeThumbnailsCb.IsChecked = s.LargeThumbnails;
|
||||
@@ -327,10 +345,9 @@ public partial class Settings : UserControl
|
||||
s.VehiclesVolume = (int)VehiclesVolumeSlider.Value;
|
||||
s.PositionalVolume = (int)PositionalVolumeSlider.Value;
|
||||
s.AmbientVolume = (int)AmbientVolumeSlider.Value;
|
||||
s.VolumePaused = (int)VolumePausedSlider.Value;
|
||||
s.SkipPipeline = IsChecked(SkipPipelineCb);
|
||||
s.DebugLogVisible = IsChecked(DebugLogVisibleCb);
|
||||
s.PyScreenPriority = (PyScreenPriorityCb.SelectedItem as ComboBoxItem)?.Tag as string ?? "normal";
|
||||
s.PythonScreenUpdateRate = ParsePriorityTag((PyScreenPriorityCb.SelectedItem as ComboBoxItem)?.Tag);
|
||||
|
||||
s.AutoCloseStarter = IsChecked(AutoCloseStarterCb);
|
||||
s.LargeThumbnails = IsChecked(LargeThumbnailsCb);
|
||||
@@ -344,31 +361,39 @@ public partial class Settings : UserControl
|
||||
new UartWindow().ShowDialog(owner);
|
||||
}
|
||||
|
||||
private void AboutButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this) is Window owner)
|
||||
AboutWindow.Create().ShowDialog(owner);
|
||||
}
|
||||
|
||||
private void SaveButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ReadFromUi();
|
||||
StarterNG.Classes.Settings.Instance.Save();
|
||||
KeyboardConfig.Instance.Save();
|
||||
RedrawThumbnailsIfNeeded();
|
||||
_dirty = false;
|
||||
UpdateSaveState();
|
||||
if (SaveStatus is not null)
|
||||
SaveStatus.Text = App.Loc["SettingsSaved"];
|
||||
}
|
||||
|
||||
private void RedrawThumbnailsIfNeeded()
|
||||
{
|
||||
bool large = StarterNG.Classes.Settings.Instance.LargeThumbnails;
|
||||
if (large == _drawnThumbs)
|
||||
return;
|
||||
|
||||
_drawnThumbs = large;
|
||||
ThumbnailSizeChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void ResetButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
StarterNG.Classes.Settings.Instance.Load();
|
||||
ApplyToUi();
|
||||
RedrawThumbnailsIfNeeded();
|
||||
CancelCapture();
|
||||
KeyboardConfig.Instance.Load();
|
||||
RebuildBindingList();
|
||||
RebuildKeyboard();
|
||||
if (SaveStatus is not null)
|
||||
SaveStatus.Text = string.Empty;
|
||||
_dirty = false;
|
||||
UpdateSaveState();
|
||||
}
|
||||
|
||||
private void SelectResolution(int width, int height)
|
||||
@@ -404,29 +429,29 @@ public partial class Settings : UserControl
|
||||
|
||||
private void TextureResolutionSlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
if (texResolution is null || textureResolutionSlider is null)
|
||||
if (TexResolutionRow is null || textureResolutionSlider is null)
|
||||
return;
|
||||
|
||||
int resolution = 1 << (int)textureResolutionSlider.Value;
|
||||
texResolution.Text = $"{resolution} px";
|
||||
TexResolutionRow.ValueText = $"{resolution} px";
|
||||
}
|
||||
|
||||
private void CabTextureResolutionSlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
if (cabTexResolution is null || cabTextureResolutionSlider is null)
|
||||
if (CabTexResolutionRow is null || cabTextureResolutionSlider is null)
|
||||
return;
|
||||
|
||||
int resolution = 1 << (int)cabTextureResolutionSlider.Value;
|
||||
cabTexResolution.Text = $"{resolution} px";
|
||||
CabTexResolutionRow.ValueText = $"{resolution} px";
|
||||
}
|
||||
|
||||
private void shaderResolutionSlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
if (shaderResolution is null || shaderResolutionSlider is null)
|
||||
if (ShadowResolutionRow is null || shaderResolutionSlider is null)
|
||||
return;
|
||||
|
||||
int resolution = 1 << (int)shaderResolutionSlider.Value;
|
||||
shaderResolution.Text = $"{resolution} px";
|
||||
ShadowResolutionRow.ValueText = $"{resolution} px";
|
||||
ToolTip.SetTip(shaderResolutionSlider, ShadowMapWord(resolution));
|
||||
}
|
||||
|
||||
@@ -464,16 +489,196 @@ public partial class Settings : UserControl
|
||||
private void FeedbackCb_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) =>
|
||||
UpdateFeedbackDetailVisibility();
|
||||
|
||||
private void UpdateFeedbackDetailVisibility()
|
||||
/// <summary>
|
||||
/// The port and UART rows only apply to some feedback modes. Visibility is
|
||||
/// decided in one place — <see cref="ApplyFilter"/> — so that a search cannot
|
||||
/// reveal a row the selected mode does not use.
|
||||
/// </summary>
|
||||
private void UpdateFeedbackDetailVisibility() => ApplyFilter();
|
||||
|
||||
private bool ConditionAllows(SettingRow row)
|
||||
{
|
||||
FeedbackPortRow.IsVisible = FeedbackCb.SelectedIndex == 3;
|
||||
ComButton.IsVisible = FeedbackCb.SelectedIndex == 5;
|
||||
if (ReferenceEquals(row, FeedbackPortRow))
|
||||
return FeedbackCb.SelectedIndex == 3;
|
||||
if (ReferenceEquals(row, ComButtonRow))
|
||||
return FeedbackCb.SelectedIndex == 5;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Category navigation, search and unsaved-changes state ────────────────
|
||||
|
||||
private bool _dirty;
|
||||
|
||||
private void CategoryList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
// Picking a category is how you leave a search, so the query goes with it.
|
||||
if (SettingsSearch is { Text.Length: > 0 })
|
||||
SettingsSearch.Text = string.Empty; // raises TextChanged -> ApplyFilter
|
||||
else
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
private void SettingsSearch_OnTextChanged(object? sender, TextChangedEventArgs e) => ApplyFilter();
|
||||
|
||||
/// <summary>
|
||||
/// Single owner of what is on screen. With an empty query it shows the selected
|
||||
/// category; with a query it stacks every category and keeps only the rows that
|
||||
/// match, collapsing sections and categories that end up empty.
|
||||
/// </summary>
|
||||
private void ApplyFilter()
|
||||
{
|
||||
if (SettingsStack is null || CategoryList is null || ControlsHost is null)
|
||||
return;
|
||||
|
||||
string query = SettingsSearch?.Text?.Trim() ?? string.Empty;
|
||||
bool searching = query.Length > 0;
|
||||
string category = (CategoryList.SelectedItem as ListBoxItem)?.Tag as string ?? string.Empty;
|
||||
|
||||
// Key bindings are their own world: they bring their own scrolling and a
|
||||
// star-sized row, so they replace the settings scroller rather than living
|
||||
// in it, and the search box filters them instead of the settings.
|
||||
bool bindings = category == "controls";
|
||||
ControlsHost.IsVisible = bindings;
|
||||
SettingsScroll.IsVisible = !bindings;
|
||||
|
||||
if (bindings)
|
||||
{
|
||||
RebuildBindingList();
|
||||
foreach (var entry in CategoryList.Items.OfType<ListBoxItem>())
|
||||
entry.Classes.Set("NoHits", false);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var panel in SettingsStack.Children.OfType<StackPanel>())
|
||||
{
|
||||
bool anyRow = false;
|
||||
|
||||
foreach (var section in panel.GetLogicalDescendants().OfType<SettingsSection>())
|
||||
{
|
||||
bool anyInSection = false;
|
||||
|
||||
foreach (var row in section.GetLogicalDescendants().OfType<SettingRow>())
|
||||
{
|
||||
bool visible = ConditionAllows(row) &&
|
||||
(!searching || TextMatch.Contains(RowSearchText(row), query));
|
||||
row.IsVisible = visible;
|
||||
anyInSection |= visible;
|
||||
}
|
||||
|
||||
section.IsVisible = anyInSection;
|
||||
anyRow |= anyInSection;
|
||||
}
|
||||
|
||||
string tag = panel.Tag as string ?? string.Empty;
|
||||
panel.IsVisible = anyRow && (searching || tag == category);
|
||||
|
||||
// The caption only earns its place when several categories are stacked.
|
||||
foreach (var caption in panel.Children.OfType<TextBlock>())
|
||||
caption.IsVisible = searching;
|
||||
|
||||
if (CategoryItem(tag) is { } item)
|
||||
item.Classes.Set("NoHits", searching && !anyRow);
|
||||
}
|
||||
|
||||
if (searching)
|
||||
SettingsScroll.Offset = new Vector(0, 0);
|
||||
}
|
||||
|
||||
private ListBoxItem? CategoryItem(string tag) =>
|
||||
CategoryList.Items.OfType<ListBoxItem>()
|
||||
.FirstOrDefault(i => string.Equals(i.Tag as string, tag, StringComparison.Ordinal));
|
||||
|
||||
/// <summary>
|
||||
/// Everything about a row a user might type: its label, its explanation and
|
||||
/// the text of the control itself (a checkbox caption, a dropdown's options).
|
||||
/// </summary>
|
||||
private static string RowSearchText(SettingRow row)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(row.Label).Append(' ').Append(row.Description).Append(' ');
|
||||
|
||||
switch (row.Content)
|
||||
{
|
||||
case CheckBox cb:
|
||||
sb.Append(cb.Content);
|
||||
break;
|
||||
case Button btn:
|
||||
sb.Append(btn.Content);
|
||||
break;
|
||||
case ComboBox combo:
|
||||
foreach (var item in combo.Items.OfType<ComboBoxItem>())
|
||||
sb.Append(item.Content).Append(' ');
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes every input in the panel to <see cref="MarkDirty"/> once, so the
|
||||
/// Save button can tell the user there is something to save. Section master
|
||||
/// switches hang off SettingsSection.Toggle rather than the logical tree, so
|
||||
/// they are collected separately.
|
||||
/// </summary>
|
||||
private void HookDirtyTracking()
|
||||
{
|
||||
foreach (var control in this.GetLogicalDescendants().OfType<Control>())
|
||||
{
|
||||
switch (control)
|
||||
{
|
||||
// Choosing a profile changes nothing until Apply is pressed.
|
||||
case ComboBox combo when ReferenceEquals(combo, ProfileCb):
|
||||
break;
|
||||
case CheckBox cb:
|
||||
cb.IsCheckedChanged += (_, _) => MarkDirty();
|
||||
break;
|
||||
case Slider slider:
|
||||
slider.ValueChanged += (_, _) => MarkDirty();
|
||||
break;
|
||||
case ComboBox combo:
|
||||
combo.SelectionChanged += (_, _) => MarkDirty();
|
||||
break;
|
||||
case NumericUpDown nud:
|
||||
nud.ValueChanged += (_, _) => MarkDirty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var section in this.GetLogicalDescendants().OfType<SettingsSection>())
|
||||
if (section.Toggle is CheckBox toggle)
|
||||
toggle.IsCheckedChanged += (_, _) => MarkDirty();
|
||||
}
|
||||
|
||||
private void MarkDirty()
|
||||
{
|
||||
if (_loading || _dirty)
|
||||
return;
|
||||
|
||||
_dirty = true;
|
||||
UpdateSaveState();
|
||||
}
|
||||
|
||||
private void UpdateSaveState()
|
||||
{
|
||||
if (SaveButton is null || SaveStatus is null)
|
||||
return;
|
||||
|
||||
bool dirty = _dirty || KeyboardConfig.Instance.Dirty;
|
||||
SaveButton.Classes.Set("Accent", dirty);
|
||||
SaveStatus.Text = dirty ? App.Loc["UnsavedChanges"] : string.Empty;
|
||||
}
|
||||
|
||||
private static bool IsChecked(CheckBox cb) => cb.IsChecked == true;
|
||||
|
||||
private static int Clamp(int v, int lo, int hi) => v < lo ? lo : (v > hi ? hi : v);
|
||||
|
||||
private static int ParsePriorityTag(object? tag)
|
||||
{
|
||||
if (tag is int i) return i;
|
||||
if (tag is string s && int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out int n)) return n;
|
||||
return 200;
|
||||
}
|
||||
|
||||
private static int Log2(int value, int fallback)
|
||||
{
|
||||
if (value <= 0) return fallback;
|
||||
@@ -490,10 +695,15 @@ public partial class Settings : UserControl
|
||||
return 4;
|
||||
}
|
||||
|
||||
private TextBox? _controlsSearch;
|
||||
private StackPanel? _bindingListPanel;
|
||||
private StackPanel? _keyboardPanel;
|
||||
|
||||
/// <summary>
|
||||
/// True while a cab-control binding is waiting for a keypress. The window's own
|
||||
/// shortcuts stand down for the duration, otherwise Ctrl+N would never reach here.
|
||||
/// </summary>
|
||||
public bool IsCapturingKey => _capturing is not null;
|
||||
|
||||
private KeyBinding? _capturing;
|
||||
private Button? _capturingButton;
|
||||
|
||||
@@ -527,10 +737,6 @@ public partial class Settings : UserControl
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Foreground = FgDimBrush
|
||||
});
|
||||
_controlsSearch = new TextBox { Width = 240, PlaceholderText = App.Loc["Search"] };
|
||||
_controlsSearch.TextChanged += (_, _) => RebuildBindingList();
|
||||
topBar.Children.Add(_controlsSearch);
|
||||
|
||||
var restoreBtn = new Button { Content = App.Loc["RestoreDefaults"] };
|
||||
restoreBtn.Classes.Add("Flat");
|
||||
restoreBtn.Click += (_, _) =>
|
||||
@@ -538,6 +744,7 @@ public partial class Settings : UserControl
|
||||
CancelCapture();
|
||||
KeyboardConfig.Instance.LoadDefaults();
|
||||
KeyboardConfig.Instance.Dirty = true;
|
||||
MarkDirty();
|
||||
RebuildBindingList();
|
||||
RebuildKeyboard();
|
||||
};
|
||||
@@ -576,7 +783,8 @@ public partial class Settings : UserControl
|
||||
return;
|
||||
|
||||
_bindingListPanel.Children.Clear();
|
||||
string filter = _controlsSearch?.Text?.Trim().ToLowerInvariant() ?? string.Empty;
|
||||
// The sidebar search box serves this list too - see ApplyFilter.
|
||||
string filter = SettingsSearch?.Text?.Trim() ?? string.Empty;
|
||||
var conflicts = ComputeConflicts();
|
||||
|
||||
foreach (var b in KeyboardConfig.Instance.Bindings)
|
||||
@@ -588,8 +796,8 @@ public partial class Settings : UserControl
|
||||
}
|
||||
|
||||
private static bool MatchesFilter(KeyBinding b, string filter) =>
|
||||
b.Command.ToLowerInvariant().Contains(filter) ||
|
||||
b.Description.ToLowerInvariant().Contains(filter);
|
||||
TextMatch.Contains(b.Command, filter) ||
|
||||
TextMatch.Contains(b.Description, filter);
|
||||
|
||||
private Control BuildBindingRow(KeyBinding b, HashSet<string> conflicts)
|
||||
{
|
||||
@@ -604,14 +812,14 @@ public partial class Settings : UserControl
|
||||
{
|
||||
Text = CommandLabel(b),
|
||||
Foreground = FgBrush,
|
||||
FontSize = 13,
|
||||
FontSize = 12,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis
|
||||
});
|
||||
label.Children.Add(new TextBlock
|
||||
{
|
||||
Text = b.Command,
|
||||
Foreground = FgDimBrush,
|
||||
FontSize = 11,
|
||||
FontSize = 12,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis
|
||||
});
|
||||
Grid.SetColumn(label, 0);
|
||||
@@ -647,6 +855,7 @@ public partial class Settings : UserControl
|
||||
b.Shift = b.Ctrl = false;
|
||||
b.Key = "none";
|
||||
KeyboardConfig.Instance.Dirty = true;
|
||||
MarkDirty();
|
||||
RebuildBindingList();
|
||||
RebuildKeyboard();
|
||||
};
|
||||
@@ -710,6 +919,7 @@ public partial class Settings : UserControl
|
||||
_capturing.Ctrl = e.KeyModifiers.HasFlag(KeyModifiers.Control);
|
||||
_capturing.Key = token;
|
||||
KeyboardConfig.Instance.Dirty = true;
|
||||
MarkDirty();
|
||||
e.Handled = true;
|
||||
|
||||
_capturing = null;
|
||||
@@ -912,7 +1122,7 @@ public partial class Settings : UserControl
|
||||
{
|
||||
Text = App.Loc["BindKeyHint"],
|
||||
Foreground = FgDimBrush,
|
||||
FontSize = 11,
|
||||
FontSize = 12,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
});
|
||||
panel.Children.Add(grid);
|
||||
@@ -957,6 +1167,7 @@ public partial class Settings : UserControl
|
||||
}
|
||||
|
||||
KeyboardConfig.Instance.Dirty = true;
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
private static KeyBinding? CommandInSlot(string token, bool shift, bool ctrl) =>
|
||||
@@ -1074,25 +1285,25 @@ public partial class Settings : UserControl
|
||||
{
|
||||
var q = QualityWords;
|
||||
|
||||
if (texFilteringValue != null)
|
||||
texFilteringValue.Text = q[Clamp((int)TexFilteringSlider.Value, 1, 5) - 1];
|
||||
if (TexFilteringRow != null)
|
||||
TexFilteringRow.ValueText = q[Clamp((int)TexFilteringSlider.Value, 1, 5) - 1];
|
||||
|
||||
string[] four = { q[0], q[1], q[3], q[4] };
|
||||
if (trackCurvesValue != null)
|
||||
trackCurvesValue.Text = four[Clamp((int)TrackCurvesSlider.Value, 1, 4) - 1];
|
||||
if (smokeParticlesValue != null)
|
||||
smokeParticlesValue.Text = four[Clamp((int)SmokeParticlesSlider.Value, 1, 4) - 1];
|
||||
if (TrackCurvesRow != null)
|
||||
TrackCurvesRow.ValueText = four[Clamp((int)TrackCurvesSlider.Value, 1, 4) - 1];
|
||||
if (SmokeParticlesRow != null)
|
||||
SmokeParticlesRow.ValueText = four[Clamp((int)SmokeParticlesSlider.Value, 1, 4) - 1];
|
||||
|
||||
string[] msaa = { App.Loc["MsaaNone"], "x2", "x4", "x8" };
|
||||
if (multisamplingValue != null)
|
||||
multisamplingValue.Text = msaa[Clamp((int)MultisamplingSlider.Value, 1, 4) - 1];
|
||||
if (MultisamplingRow != null)
|
||||
MultisamplingRow.ValueText = msaa[Clamp((int)MultisamplingSlider.Value, 1, 4) - 1];
|
||||
|
||||
string[] range = { App.Loc["RangeNormal"], App.Loc["RangeHigh"], App.Loc["RangeVeryHigh"] };
|
||||
if (renderRangeValue != null)
|
||||
renderRangeValue.Text = range[Clamp((int)RenderRangeSlider.Value, 1, 3) - 1];
|
||||
if (RenderRangeRow != null)
|
||||
RenderRangeRow.ValueText = range[Clamp((int)RenderRangeSlider.Value, 1, 3) - 1];
|
||||
|
||||
if (cursorSensitivityValue != null)
|
||||
cursorSensitivityValue.Text = (int)CursorSensitivitySlider.Value switch
|
||||
if (CursorSensitivityRow != null)
|
||||
CursorSensitivityRow.ValueText = (int)CursorSensitivitySlider.Value switch
|
||||
{
|
||||
<= 1 => q[1],
|
||||
2 => App.Loc["QualityStandard"],
|
||||
@@ -1100,25 +1311,25 @@ public partial class Settings : UserControl
|
||||
_ => q[4]
|
||||
};
|
||||
|
||||
if (shaderRangeValue != null)
|
||||
if (ShadowRangeRow != null)
|
||||
{
|
||||
int metres = (int)shaderRange.Value;
|
||||
shaderRangeValue.Text = $"{metres} m";
|
||||
ShadowRangeRow.ValueText = $"{metres} m";
|
||||
ToolTip.SetTip(shaderRange, ShadowRangeWord(metres));
|
||||
}
|
||||
|
||||
if (cabShadowRangeValue != null)
|
||||
if (CabShadowRangeRow != null)
|
||||
{
|
||||
int metres = (int)cabShaderSourceRange.Value;
|
||||
cabShadowRangeValue.Text = metres <= 0 ? App.Loc["RangeDisabled"] : $"{metres} m";
|
||||
CabShadowRangeRow.ValueText = metres <= 0 ? App.Loc["RangeDisabled"] : $"{metres} m";
|
||||
ToolTip.SetTip(cabShaderSourceRange, CabShadowRangeWord(metres));
|
||||
}
|
||||
|
||||
if (reflectionsFramerateValue != null)
|
||||
if (ReflectionsFramerateRow != null)
|
||||
{
|
||||
int fps = (int)reflectionsFramerate.Value;
|
||||
string word = ReflectionsRefreshWord(fps);
|
||||
reflectionsFramerateValue.Text = $"{fps} FPS";
|
||||
ReflectionsFramerateRow.ValueText = $"{fps} FPS";
|
||||
ToolTip.SetTip(reflectionsFramerate, word.Length == 0 ? null : word);
|
||||
}
|
||||
}
|
||||
|
||||
103
StarterNG/Views/ShortcutsWindow.cs
Normal file
103
StarterNG/Views/ShortcutsWindow.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
|
||||
using StarterNG.Infrastructure;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
/// <summary>
|
||||
/// The cheat sheet, printed straight from <see cref="Shortcuts.All"/> so it can never
|
||||
/// list a gesture the window does not actually dispatch.
|
||||
/// </summary>
|
||||
public static class ShortcutsWindow
|
||||
{
|
||||
public static Window Create()
|
||||
{
|
||||
var list = new StackPanel { Spacing = 2, Margin = new Thickness(20, 12, 20, 20) };
|
||||
|
||||
foreach (var scope in new[] { ShortcutScope.Global, ShortcutScope.Depot, ShortcutScope.Consist })
|
||||
{
|
||||
list.Children.Add(new TextBlock
|
||||
{
|
||||
Text = Shortcuts.ScopeLabel(scope),
|
||||
FontWeight = FontWeight.Bold,
|
||||
Margin = new Thickness(0, 12, 0, 4)
|
||||
});
|
||||
|
||||
foreach (var s in Shortcuts.InScope(scope))
|
||||
list.Children.Add(Row(s.Display, App.Loc[s.LabelKey]));
|
||||
}
|
||||
|
||||
list.Children.Add(new TextBlock
|
||||
{
|
||||
Text = App.Loc["ShortcutsTabHint"],
|
||||
Opacity = 0.7,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 16, 0, 0)
|
||||
});
|
||||
|
||||
var win = new Window
|
||||
{
|
||||
Title = App.Loc["ShortcutsTitle"],
|
||||
Width = 460,
|
||||
Height = 560,
|
||||
MinWidth = 380,
|
||||
MinHeight = 320,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
Content = new ScrollViewer
|
||||
{
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
Content = list
|
||||
}
|
||||
};
|
||||
|
||||
win.KeyDown += (_, e) =>
|
||||
{
|
||||
if (e.Key is Key.Escape or Key.F1)
|
||||
{
|
||||
win.Close();
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
return win;
|
||||
}
|
||||
|
||||
private static Control Row(string gesture, string label)
|
||||
{
|
||||
var grid = new Grid { ColumnDefinitions = new ColumnDefinitions("130,12,*") };
|
||||
|
||||
var keys = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.Parse("#2A3037")),
|
||||
BorderBrush = new SolidColorBrush(Color.Parse("#3A424A")),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(3),
|
||||
Padding = new Thickness(6, 1),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = gesture,
|
||||
FontFamily = new FontFamily("Consolas, Menlo, monospace"),
|
||||
FontSize = 12
|
||||
}
|
||||
};
|
||||
|
||||
var text = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 12,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
Grid.SetColumn(text, 2);
|
||||
|
||||
grid.Children.Add(keys);
|
||||
grid.Children.Add(text);
|
||||
grid.Margin = new Thickness(0, 2);
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
</Grid>
|
||||
|
||||
<TextBlock Name="statusText" DockPanel.Dock="Bottom" Margin="0,6,0,0"
|
||||
FontSize="11" Opacity="0.75" />
|
||||
FontSize="12" Opacity="0.75" />
|
||||
|
||||
<Grid DockPanel.Dock="Left" Width="260" RowDefinitions="*,4,*">
|
||||
<ListBox Name="typeList" Grid.Row="0" Classes="Compact" />
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class UartWindow : Window
|
||||
root.Children.Add(new TextBlock
|
||||
{
|
||||
Text = App.Loc["UartHint"],
|
||||
FontSize = 11,
|
||||
FontSize = 12,
|
||||
Opacity = 0.65,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<String key="OptionalIncludes">Volitelné prvky</String>
|
||||
<String key="OpenTextureFolder">Otevřít složku s texturami</String>
|
||||
<String key="CopyTextureName">Kopírovat název</String>
|
||||
<String key="CopyCoupler">Kopírovat spřáhlo z předchozího</String>
|
||||
<String key="CopyCoupler">Kopírovat z předchozího</String>
|
||||
<String key="ThermoAmbient">Chladivo na okolní teplotě</String>
|
||||
<String key="TextureInfo">Textura</String>
|
||||
<String key="TexOperator">Dopravce</String>
|
||||
@@ -185,6 +185,9 @@
|
||||
<String key="SwitchNormal">N - brzdič zabržděný</String>
|
||||
<String key="SwitchOff">0 - odbržděný, odpuštěný</String>
|
||||
<String key="SwitchOff10">1 - odbržděný, odp. z 10% pravd.</String>
|
||||
<String key="SwitchEmergencyTest"><> - vyp. na zkoušku rychlobrzdy</String>
|
||||
<String key="SwitchAgonal">X - dosluhující (20% vyp., náhodně)</String>
|
||||
<String key="SwitchEmptied">E - vyprázdněný (potrubí na 0)</String>
|
||||
|
||||
<!-- Damage (wheel parameters) -->
|
||||
<String key="Damage">Poškození</String>
|
||||
@@ -194,15 +197,24 @@
|
||||
<String key="DamageFlatnessProb">Pravděpodobnost plochého místa (%)</String>
|
||||
<String key="LoadType">Druh nákladu</String>
|
||||
<String key="LoadCount">Množství</String>
|
||||
<String key="LoadUnitPieces">ks</String>
|
||||
<String key="LoadUnitTons">t</String>
|
||||
<String key="LoadUnitGeneric">j.</String>
|
||||
<String key="LoadHint">Pro naložení vozidla zadejte množství větší než 0.</String>
|
||||
<String key="LoadNotLoadable">Toto vozidlo nepřijímá žádný náklad.</String>
|
||||
<String key="LoadPantState">Zvednutý pantograf na startu</String>
|
||||
<String key="LoadPantStateHint">Množství určuje pantograf: 1 — A, 2 — B, 3 — oba.</String>
|
||||
<String key="Pantograph">Pantograf</String>
|
||||
<String key="PantStateNone">Žádný</String>
|
||||
<String key="PantStateBoth">Oba</String>
|
||||
<String key="WagonNumber">Číslo vozu</String>
|
||||
<String key="LoadCopyPrev">Kopírovat náklad z předchozího vozidla</String>
|
||||
<String key="LoadMax">Maximální možný náklad</String>
|
||||
<String key="LoadPrevShort">Z předchozího</String>
|
||||
<String key="LoadToAllShort">Na následující</String>
|
||||
<String key="LoadMaxShort">Maximální</String>
|
||||
<String key="ConsistLoadTools">Náklad celé soupravy</String>
|
||||
<String key="ConsistRandomType">Náhodný náklad</String>
|
||||
<String key="ConsistRandomType">Náhodný druh</String>
|
||||
<String key="ConsistMaxAmount">Maximální množství</String>
|
||||
<String key="ConsistRandomAmount">Náhodné množství</String>
|
||||
<String key="Set">Nastavit</String>
|
||||
@@ -252,6 +264,7 @@
|
||||
<String key="CouplingUnitRear">Spřáhlo na konci jednotky</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="AddVehicle">Přidat</String>
|
||||
<String key="TipAddVehicle">Přidat toto vozidlo do soupravy</String>
|
||||
<String key="TipAddUnit">Přidat celou jednotku do soupravy</String>
|
||||
@@ -278,7 +291,7 @@
|
||||
<String key="Fullscreen">Celá obrazovka</String>
|
||||
<String key="PauseSimInactive">Pozastavit simulaci při neaktivním okně</String>
|
||||
<String key="PauseSimStart">Pozastavit po spuštění</String>
|
||||
<String key="CursorSensitivity">Citlivost kurzoru:</String>
|
||||
<String key="CursorSensitivity">Citlivost kurzoru</String>
|
||||
<String key="MouseHorInvert">Převrátit myš vodorovně</String>
|
||||
<String key="MouseVertInvert">Převrátit myš svisle</String>
|
||||
|
||||
@@ -309,7 +322,7 @@
|
||||
<String key="UartRadioChannel">Kanál rádia</String>
|
||||
|
||||
<String key="Others">Ostatní</String>
|
||||
<String key="SelectEXE">Vybrat exe:</String>
|
||||
<String key="SelectEXE">Vybrat exe</String>
|
||||
<String key="SelectEXEAuto">Vybrat automaticky</String>
|
||||
<String key="DebugMode">Režim ladění (testovací režim)</String>
|
||||
<String key="VirtualShunting">Virtuální posun</String>
|
||||
@@ -324,12 +337,12 @@
|
||||
<String key="REOff">Vypnout vizuální vrstvu</String>
|
||||
<String key="REExperimental">Experimentální</String>
|
||||
|
||||
<String key="Resolution">Rozlišení:</String>
|
||||
<String key="BufforResolution">Rozlišení bufferu:</String>
|
||||
<String key="MaxTexResolution">Maximální rozlišení textur:</String>
|
||||
<String key="MaxTexCabResolution">Maximální rozlišení textur kabiny:</String>
|
||||
<String key="TexFilteringQuality">Kvalita filtrování textur:</String>
|
||||
<String key="Multisampling">Multisampling:</String>
|
||||
<String key="Resolution">Rozlišení</String>
|
||||
<String key="BufforResolution">Rozlišení bufferu</String>
|
||||
<String key="MaxTexResolution">Maximální rozlišení textur</String>
|
||||
<String key="MaxTexCabResolution">Maximální rozlišení textur kabiny</String>
|
||||
<String key="TexFilteringQuality">Kvalita filtrování textur</String>
|
||||
<String key="Multisampling">Multisampling</String>
|
||||
<String key="QualityVeryLow">Velmi nízká</String>
|
||||
<String key="QualityLow">Nízká</String>
|
||||
<String key="QualityNormal">Normální</String>
|
||||
@@ -346,8 +359,8 @@
|
||||
<String key="RangeHigh">Vysoká</String>
|
||||
<String key="RangeVeryHigh">Velmi vysoká</String>
|
||||
<String key="RangeDisabled">Vypnuté</String>
|
||||
<String key="DynamicLights">Dynamické zdroje světla:</String>
|
||||
<String key="RenderRange">Dohlednost vykreslování:</String>
|
||||
<String key="DynamicLights">Dynamické zdroje světla</String>
|
||||
<String key="RenderRange">Dohlednost vykreslování</String>
|
||||
<String key="FpsLimitEnable">Omezit snímkovou frekvenci</String>
|
||||
<String key="ShadowAngleLimit">Omezení délky stínů</String>
|
||||
<String key="ShadowLimitNone">Bez omezení</String>
|
||||
@@ -362,6 +375,7 @@
|
||||
<String key="MotionBlur">Rozmazání pohybem</String>
|
||||
<String key="AdditionalShadersEffects">Další efekty shaderů</String>
|
||||
<String key="ReflectionsCubeMap">Odrazy jako cubemapa</String>
|
||||
<String key="SkipPipeline">SkipPipeline</String>
|
||||
<String key="RenderVBO">Vykreslování VBO</String>
|
||||
<String key="RenderShadows">Vykreslovat stíny</String>
|
||||
<String key="ReflectionsFramerate">Frekvence obnovy odrazů</String>
|
||||
@@ -419,14 +433,14 @@
|
||||
<String key="IgnoreIrrelevantTrains">Přeskočit dekorační soupravy při startu</String>
|
||||
|
||||
<String key="Starter">Starter</String>
|
||||
<String key="SettingsProfile">Profil nastavení:</String>
|
||||
<String key="SettingsProfile">Profil nastavení</String>
|
||||
<String key="ProfileApply">Použít</String>
|
||||
<String key="ProfileSaveAs">Uložit jako…</String>
|
||||
<String key="ProfileNamePrompt">Název profilu</String>
|
||||
<String key="AutoCloseStarter">Automaticky zavřít starter</String>
|
||||
<String key="LargeThumbnails">Velké náhledy</String>
|
||||
<String key="AutoExpandSceneryTree">Automaticky rozbalit strom scenérií</String>
|
||||
<String key="BatteryDefault">Baterie při spuštění:</String>
|
||||
<String key="BatteryDefault">Baterie při spuštění</String>
|
||||
<String key="BatteryDefaultScenario">Podle scénáře</String>
|
||||
<String key="BatteryDefaultOff">Vždy vypnutá</String>
|
||||
<String key="BatteryDefaultOn">Vždy zapnutá</String>
|
||||
@@ -455,6 +469,7 @@
|
||||
<String key="Save">Uložit</String>
|
||||
<String key="Reset">Obnovit</String>
|
||||
<String key="SettingsSaved">Nastavení uloženo</String>
|
||||
<String key="UnsavedChanges">Neuložené změny</String>
|
||||
|
||||
<!-- Errors / diagnostics -->
|
||||
<String key="FaultTitle">Chyba</String>
|
||||
@@ -482,6 +497,7 @@
|
||||
<String key="GeneralInfo">Obecné</String>
|
||||
<String key="Model">Model</String>
|
||||
<String key="VMax">VMax</String>
|
||||
<String key="Name">Název</String>
|
||||
<String key="Texture">Textura</String>
|
||||
|
||||
<!-- Scenery tab -->
|
||||
@@ -509,9 +525,9 @@
|
||||
<String key="WarehouseImportFile">Přidat soupravy ze souboru...</String>
|
||||
<String key="StartWithoutSaving">Spustit bez uložení nastavení</String>
|
||||
<String key="StartFreeFly">Spustit bez vozidla</String>
|
||||
<String key="VolumePaused">Hlasitost při pauze:</String>
|
||||
<String key="VolumePaused">Hlasitost při pauze</String>
|
||||
<String key="DebugLogVisible">Zobrazovat průběh simulace</String>
|
||||
<String key="PyScreenPriority">Frekvence obnovení doplňkových obrazovek:</String>
|
||||
<String key="PyScreenPriority">Frekvence obnovení doplňkových obrazovek</String>
|
||||
<String key="PyPriorityNormal">Standardní</String>
|
||||
<String key="PyPriorityLower">Nízká</String>
|
||||
<String key="PyPriorityLowest">Velmi nízká</String>
|
||||
@@ -531,7 +547,7 @@
|
||||
<String key="TexWorks">Revize:</String>
|
||||
<String key="RevisionFrom">Revize od:</String>
|
||||
<String key="RevisionTo">Revize do:</String>
|
||||
<String key="CrewLabel">Obsazení:</String>
|
||||
<String key="CrewLabel">Obsazení</String>
|
||||
<String key="RuleAdd">Přidat do pravidla</String>
|
||||
<String key="RuleNew">— nové pravidlo —</String>
|
||||
<String key="RuleRemove">Odebrat z pravidla</String>
|
||||
@@ -551,4 +567,18 @@
|
||||
<String key="AdvLogPage">Deník událostí</String>
|
||||
<String key="AdvClearLog">Vymazat log</String>
|
||||
<String key="ConsistSchedule">Sestava</String>
|
||||
|
||||
<!-- Klávesové zkratky -->
|
||||
<String key="ShortcutsTitle">Klávesové zkratky</String>
|
||||
<String key="ShortcutsGlobal">Obecné</String>
|
||||
<String key="ShortcutsTabHint">Tab přepíná mezi panely, šipky fungují uvnitř seznamů. V pásu soupravy Tab skočí na vybrané vozidlo.</String>
|
||||
<String key="ShortcutShowHelp">Tento seznam</String>
|
||||
<String key="ShortcutFocusSearch">Přejít do vyhledávání vozidel</String>
|
||||
<String key="ShortcutAddVehicle">Přidat vybrané vozidlo do soupravy</String>
|
||||
<String key="ShortcutSetNumber">Nastavit číslo vozu</String>
|
||||
<String key="ShortcutPrevNext">Předchozí / další vozidlo</String>
|
||||
<String key="ShortcutFirstLast">První / poslední vozidlo</String>
|
||||
<String key="ShortcutMember">Vybrat člen ve spojené jednotce</String>
|
||||
<String key="ShortcutMoveVehicle">Posunout vozidlo v soupravě</String>
|
||||
<String key="ShortcutCardMenu">Otevřít nabídku vozidla</String>
|
||||
</Language>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<String key="OptionalIncludes">Optional elements</String>
|
||||
<String key="OpenTextureFolder">Open texture folder</String>
|
||||
<String key="CopyTextureName">Copy name</String>
|
||||
<String key="CopyCoupler">Copy coupler from previous</String>
|
||||
<String key="CopyCoupler">Copy from previous</String>
|
||||
<String key="ThermoAmbient">Coolant at ambient temperature</String>
|
||||
<String key="TextureInfo">Texture</String>
|
||||
<String key="TexOperator">Operator</String>
|
||||
@@ -185,6 +185,9 @@
|
||||
<String key="SwitchNormal">N - On</String>
|
||||
<String key="SwitchOff">0 - Off, slack</String>
|
||||
<String key="SwitchOff10">1 - Off, slack (10% chance)</String>
|
||||
<String key="SwitchEmergencyTest"><> - off for emergency brake test</String>
|
||||
<String key="SwitchAgonal">X - worn out (20% off, random)</String>
|
||||
<String key="SwitchEmptied">E - emptied (pipes at 0)</String>
|
||||
|
||||
<!-- Damage (wheel parameters) -->
|
||||
<String key="Damage">Damage</String>
|
||||
@@ -194,15 +197,24 @@
|
||||
<String key="DamageFlatnessProb">Flat-spot probability (%)</String>
|
||||
<String key="LoadType">Load type</String>
|
||||
<String key="LoadCount">Amount</String>
|
||||
<String key="LoadUnitPieces">pcs</String>
|
||||
<String key="LoadUnitTons">t</String>
|
||||
<String key="LoadUnitGeneric">u.</String>
|
||||
<String key="LoadHint">Set an amount above 0 to load the vehicle.</String>
|
||||
<String key="LoadNotLoadable">This vehicle accepts no load.</String>
|
||||
<String key="LoadPantState">Pantograph raised at start</String>
|
||||
<String key="LoadPantStateHint">The amount selects the pantograph: 1 — A, 2 — B, 3 — both.</String>
|
||||
<String key="Pantograph">Pantograph</String>
|
||||
<String key="PantStateNone">None</String>
|
||||
<String key="PantStateBoth">Both</String>
|
||||
<String key="WagonNumber">Wagon number</String>
|
||||
<String key="LoadCopyPrev">Copy load from previous vehicle</String>
|
||||
<String key="LoadMax">Maximum possible load</String>
|
||||
<String key="LoadPrevShort">From previous</String>
|
||||
<String key="LoadToAllShort">To following</String>
|
||||
<String key="LoadMaxShort">Maximum</String>
|
||||
<String key="ConsistLoadTools">Whole-consist load</String>
|
||||
<String key="ConsistRandomType">Random load</String>
|
||||
<String key="ConsistRandomType">Random type</String>
|
||||
<String key="ConsistMaxAmount">Maximum amount</String>
|
||||
<String key="ConsistRandomAmount">Random amount</String>
|
||||
<String key="Set">Set</String>
|
||||
@@ -252,6 +264,7 @@
|
||||
<String key="CouplingUnitRear">Coupler at the rear of the unit</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="AddVehicle">Add</String>
|
||||
<String key="TipAddVehicle">Add this vehicle to the consist</String>
|
||||
<String key="TipAddUnit">Add the whole unit to the consist</String>
|
||||
@@ -278,23 +291,26 @@
|
||||
<String key="Fullscreen">Fullscreen</String>
|
||||
<String key="PauseSimInactive">Pause simulation when window is inactive</String>
|
||||
<String key="PauseSimStart">Pause on start</String>
|
||||
<String key="CursorSensitivity">Cursor sensitivity:</String>
|
||||
<String key="CursorSensitivity">Camera rotation sensitivity (horizontal)</String>
|
||||
<String key="MouseHorInvert">Invert mouse horizontally</String>
|
||||
<String key="MouseVertInvert">Invert mouse vertically</String>
|
||||
|
||||
<String key="Communication">Communication</String>
|
||||
<String key="Communication">External devices</String>
|
||||
|
||||
<String key="GamepadSignalIgnore">Ignore gamepad input</String>
|
||||
<String key="GamepadSignalIgnore">Ignore gamepad control</String>
|
||||
<String key="GamepadSignalIgnoreDesc">Disables gamepad input even when one is connected.</String>
|
||||
|
||||
<String key="Feedback">Feedback</String>
|
||||
<String key="FeedbackDesc">How cab feedback signals are delivered to an external panel. Keyboard modes flash LEDs: (1) Caps Lock = CA or SHP, Scroll Lock = starting resistances; (2) Caps Lock = CA, Scroll Lock = SHP. Other modes require an LPT, PoKeys55 or serial interface.</String>
|
||||
<String key="FeedbackOff">Off</String>
|
||||
<String key="FeedbackCASHP">Caps: CA/SHP; ScrollLock: starting resistances</String>
|
||||
<String key="FeedbackCA">Caps: CA; ScrollLock: SHP</String>
|
||||
<String key="FeedbackLPT">LPT</String>
|
||||
<String key="FeedbackPoKeys">PoKeys55</String>
|
||||
<String key="FeedbackSP">Serial port (COM)</String>
|
||||
<String key="FeedbackCASHP">Keyboard: Caps = CA+SHP, Scroll = starting resistances</String>
|
||||
<String key="FeedbackCA">Keyboard: Caps = CA, Scroll = SHP</String>
|
||||
<String key="FeedbackLPT">LPT (inpout32)</String>
|
||||
<String key="FeedbackPoKeys">PoKeys55 (USB)</String>
|
||||
<String key="FeedbackSP">COM / serial port</String>
|
||||
|
||||
<String key="FeedbackPort">LPT port address</String>
|
||||
<String key="FeedbackPortDesc">LPT port address used by the selected feedback mode.</String>
|
||||
|
||||
<String key="UartSection">UART configuration</String>
|
||||
<String key="UartEnable">Enable UART communication</String>
|
||||
@@ -309,27 +325,34 @@
|
||||
<String key="UartRadioChannel">Radio channel</String>
|
||||
|
||||
<String key="Others">Other</String>
|
||||
<String key="SelectEXE">Select exe:</String>
|
||||
<String key="SelectEXE">Select exe</String>
|
||||
<String key="SelectEXEAuto">Select automatically</String>
|
||||
<String key="DebugMode">Debug mode (test mode)</String>
|
||||
<String key="DebugMode">Debug mode</String>
|
||||
<String key="DebugModeDesc">Unlocks debug menus, extra panels and test shortcuts.</String>
|
||||
<String key="VirtualShunting">Virtual shunting</String>
|
||||
<String key="VirtualShuntingDesc">Enables an AI assistant for coupling, uncoupling and simple shunting moves while the vehicle is stopped.</String>
|
||||
<String key="LogMissingVehicleFiles">Log missing vehicle files during export</String>
|
||||
<String key="LogMissingVehicleFilesDesc">Writes vehicles the starter could not find into the log.</String>
|
||||
|
||||
<String key="Graphic">Graphics</String>
|
||||
<String key="RenderEngine">Render engine</String>
|
||||
<String key="RenderEngineDesc">OpenGL rendering path. 'Simplified' and 'Old' use the legacy renderer.</String>
|
||||
<String key="REFull">Full</String>
|
||||
<String key="REOld">Old</String>
|
||||
<String key="RESimpShader">Simplified shader</String>
|
||||
<String key="RESimpShader">Simplified (legacy)</String>
|
||||
<String key="RESimp">Simplified</String>
|
||||
<String key="REOff">Disable visual layer</String>
|
||||
<String key="REExperimental">Experimental</String>
|
||||
|
||||
<String key="Resolution">Resolution:</String>
|
||||
<String key="BufforResolution">Buffer resolution:</String>
|
||||
<String key="MaxTexResolution">Maximum texture resolution:</String>
|
||||
<String key="MaxTexCabResolution">Maximum cabin texture resolution:</String>
|
||||
<String key="TexFilteringQuality">Texture filtering quality:</String>
|
||||
<String key="Multisampling">Multisampling:</String>
|
||||
<String key="Resolution">Resolution</String>
|
||||
<String key="BufforResolution">Buffer resolution</String>
|
||||
<String key="BufforResolutionDesc">Scales the internal render buffer relative to the window resolution.</String>
|
||||
<String key="MaxTexResolution">Maximum texture resolution</String>
|
||||
<String key="MaxTexCabResolution">Maximum cabin texture resolution</String>
|
||||
<String key="TexFilteringQuality">Texture filtering quality</String>
|
||||
<String key="TexFilteringQualityDesc">Anisotropic texture filtering level.</String>
|
||||
<String key="Multisampling">Multisampling</String>
|
||||
<String key="MultisamplingDesc">Anti-aliasing of object edges. Higher settings cost more GPU time.</String>
|
||||
<String key="QualityVeryLow">Very low</String>
|
||||
<String key="QualityLow">Low</String>
|
||||
<String key="QualityNormal">Normal</String>
|
||||
@@ -346,15 +369,20 @@
|
||||
<String key="RangeHigh">High</String>
|
||||
<String key="RangeVeryHigh">Very high</String>
|
||||
<String key="RangeDisabled">Disabled</String>
|
||||
<String key="DynamicLights">Dynamic light sources:</String>
|
||||
<String key="RenderRange">Rendering range:</String>
|
||||
<String key="DynamicLights">Dynamic light sources</String>
|
||||
<String key="DynamicLightsDesc">Maximum number of simultaneous dynamic light sources.</String>
|
||||
<String key="RenderRange">Rendering range</String>
|
||||
<String key="RenderRangeDesc">Maximum draw-distance multiplier; the engine lowers it automatically when the FPS drops.</String>
|
||||
<String key="FpsLimitEnable">Limit frame rate</String>
|
||||
<String key="ShadowAngleLimit">Shadow length limit</String>
|
||||
<String key="ShadowAngleLimitDesc">Shortens the long shadows cast by a low sun. Higher values cut them sooner.</String>
|
||||
<String key="ShadowLimitNone">No limit</String>
|
||||
<String key="ShadowLimitShort">Shortened</String>
|
||||
<String key="FullscreenWindowed">Automatic resolution (borderless fullscreen)</String>
|
||||
<String key="RenderAngleVulkan">Use ANGLE Vulkan renderer</String>
|
||||
<String key="RenderAngleVulkanDesc">Runs the ANGLE renderer on Vulkan instead of the default backend. Useful when OpenGL misbehaves.</String>
|
||||
<String key="VSync">VSync</String>
|
||||
<String key="VSyncDesc">Synchronizes frame rate with the monitor refresh rate.</String>
|
||||
<String key="SmokeDisplay">Smoke display</String>
|
||||
<String key="SmokeParticlesMultiplier">Smoke particles multiplier</String>
|
||||
|
||||
@@ -362,41 +390,61 @@
|
||||
<String key="MotionBlur">Motion blur</String>
|
||||
<String key="AdditionalShadersEffects">Additional shader effects</String>
|
||||
<String key="ReflectionsCubeMap">Reflections as cubemap</String>
|
||||
<String key="ReflectionsCubeMapDesc">Renders reflections into a cube map, so surfaces mirror the surroundings from every direction.</String>
|
||||
<String key="SkipPipeline">Simplified depth read (compatibility)</String>
|
||||
<String key="SkipPipelineDesc">Alternative depth-buffer read path with MSAA; enable it if your GPU has compatibility issues.</String>
|
||||
<String key="RenderVBO">Render VBO</String>
|
||||
<String key="RenderVBODesc">Keeps geometry in GPU buffers (faster). Disable only if you have problems with an old GPU.</String>
|
||||
<String key="RenderShadows">Render shadows</String>
|
||||
<String key="ReflectionsFramerate">Reflections refresh rate</String>
|
||||
<String key="ShadowsResolution">Shadows resolution</String>
|
||||
<String key="ShaderRange">Shadow range</String>
|
||||
<String key="CabShadowsRange">Cabin shadow sources range</String>
|
||||
<String key="CabShadowsRangeDesc">Distance at which light sources still cast shadows inside the cab. Zero turns cab shadows off.</String>
|
||||
|
||||
<String key="ShadowDisplay">Display shadows</String>
|
||||
<String key="ShadowDisplayDesc">Sets which objects cast shadows based on material priority. Lower value = fewer shadows, higher performance.</String>
|
||||
<String key="CSROnlyImportant">Only important</String>
|
||||
<String key="CSRLimited">Limited</String>
|
||||
<String key="CSRAll">All</String>
|
||||
|
||||
<String key="ReflectionsDetails">Reflections detail level</String>
|
||||
<String key="ReflectionsDetailsDesc">Which objects are rendered into the reflection cubemap.</String>
|
||||
<String key="RDTerrain">Terrain geometry only</String>
|
||||
<String key="RDTerrainModels">Terrain geometry and static models</String>
|
||||
<String key="RDTerrainModelsVehicles">Terrain geometry, static models and vehicles</String>
|
||||
|
||||
<String key="FieldOfView">View angle</String>
|
||||
<String key="FieldOfViewDesc">Vertical camera field of view in degrees (15–75).</String>
|
||||
<String key="RenderScreens">Render onboard computer screens</String>
|
||||
<String key="RenderScreensDesc">Enables cab screens driven by Python scripts.</String>
|
||||
<String key="RenderScreensThread">Render screens in a separate thread</String>
|
||||
|
||||
<String key="Physics">Physics</String>
|
||||
|
||||
<String key="TrackCurvesQuality">Track curves quality</String>
|
||||
<String key="TrackCurvesQualityDesc">How finely curved track is subdivided. Higher values are smoother but cost more CPU/GPU when loading.</String>
|
||||
<String key="PhysicsAccuracyIncreased">Increased physics accuracy</String>
|
||||
<String key="PhysicsAccuracyIncreasedDesc">Runs the full physics model with smaller time steps. More faithful, but heavier CPU load on large consists.</String>
|
||||
<String key="BreakingPantographPossibility">Possibility of pantograph breaking</String>
|
||||
<String key="BreakingPantographPossibilityDesc">Disabling protects the pantograph from breaking on faulty overhead line geometry.</String>
|
||||
<String key="PowerOverheadLineOnly">Powered only under overhead line</String>
|
||||
<String key="SaveSpeedometerTapes">Save speedometer tapes</String>
|
||||
<String key="PowerOverheadLineOnlyDesc">When disabled, a raised pantograph can power the vehicle even without touching the wire.</String>
|
||||
<String key="SaveSpeedometerTapes">Save physics parameters log</String>
|
||||
<String key="SaveSpeedometerTapesDesc">Writes a detailed trip log to the physicslog/ folder.</String>
|
||||
<String key="SaveSimLogs">Save simulation log</String>
|
||||
<String key="KeepPrevLogs">Keep previous logs</String>
|
||||
<String key="DisplaySimulation">Display simulation</String>
|
||||
<String key="KeepPrevLogsDesc">Creates a separate log file for each scenery instead of overwriting errors.txt.</String>
|
||||
<String key="DisplaySimulation">Render 3D image</String>
|
||||
<String key="DisplaySimulationDesc">When disabled, the scenery is not drawn (black screen), but the simulation keeps running.</String>
|
||||
<String key="CrashDamage">Collision damage</String>
|
||||
<String key="CrashDamageDesc">Damages couplers and consists during collisions and sharp jerks.</String>
|
||||
<String key="Friction">Friction multiplier</String>
|
||||
<String key="BrakeStep">Brake valve sensitivity</String>
|
||||
<String key="BrakeSpeed">FV4a valve sensitivity</String>
|
||||
<String key="FrictionDesc">Multiplies wheel-to-rail friction. 1.00 leaves it unchanged.</String>
|
||||
<String key="BrakeStep">Train brake step size (Num3/Num9)</String>
|
||||
<String key="BrakeStepDesc">Multiplies the size of a single step of the train brake handle controlled with Num3 and Num9. 1.00 leaves it unchanged.</String>
|
||||
<String key="BrakeSpeed">FV4a valve speed</String>
|
||||
<String key="BrakeSpeedDesc">Multiplies how fast the locomotive brake handle (FV4a) and the local brake move. 1.00 leaves it unchanged.</String>
|
||||
|
||||
<String key="Sounds">Sound</String>
|
||||
<String key="EnableSounds">Enable sound</String>
|
||||
@@ -419,14 +467,15 @@
|
||||
<String key="IgnoreIrrelevantTrains">Skip decorative consists when starting</String>
|
||||
|
||||
<String key="Starter">Starter</String>
|
||||
<String key="SettingsProfile">Settings profile:</String>
|
||||
<String key="SettingsProfile">Settings profile</String>
|
||||
<String key="ProfileApply">Apply</String>
|
||||
<String key="ProfileSaveAs">Save as…</String>
|
||||
<String key="ProfileNamePrompt">Profile name</String>
|
||||
<String key="AutoCloseStarter">Automatically close starter</String>
|
||||
<String key="LargeThumbnails">Large thumbnails</String>
|
||||
<String key="AutoExpandSceneryTree">Automatically expand scenery tree</String>
|
||||
<String key="BatteryDefault">Battery on launch:</String>
|
||||
<String key="BatteryDefault">Battery on launch</String>
|
||||
<String key="BatteryDefaultDesc">Battery state when the simulation starts, unless the scenario decides otherwise.</String>
|
||||
<String key="BatteryDefaultScenario">Scenario default</String>
|
||||
<String key="BatteryDefaultOff">Always off</String>
|
||||
<String key="BatteryDefaultOn">Always on</String>
|
||||
@@ -455,6 +504,7 @@
|
||||
<String key="Save">Save</String>
|
||||
<String key="Reset">Reset</String>
|
||||
<String key="SettingsSaved">Settings saved</String>
|
||||
<String key="UnsavedChanges">Unsaved changes</String>
|
||||
|
||||
<!-- Errors / diagnostics -->
|
||||
<String key="FaultTitle">Error</String>
|
||||
@@ -482,6 +532,7 @@
|
||||
<String key="GeneralInfo">General</String>
|
||||
<String key="Model">Model</String>
|
||||
<String key="VMax">VMax</String>
|
||||
<String key="Name">Name</String>
|
||||
<String key="Texture">Texture</String>
|
||||
|
||||
<!-- Scenery tab -->
|
||||
@@ -509,14 +560,15 @@
|
||||
<String key="WarehouseImportFile">Add consists from file...</String>
|
||||
<String key="StartWithoutSaving">Start without saving settings</String>
|
||||
<String key="StartFreeFly">Start with no vehicle</String>
|
||||
<String key="VolumePaused">Volume while paused:</String>
|
||||
<String key="DebugLogVisible">Show simulation log on screen</String>
|
||||
<String key="PyScreenPriority">Extra screen refresh rate:</String>
|
||||
<String key="PyPriorityNormal">Standard</String>
|
||||
<String key="PyPriorityLower">Low</String>
|
||||
<String key="PyPriorityLowest">Very low</String>
|
||||
<String key="PyPriorityIdle">None</String>
|
||||
<String key="PyPriorityOff">Disabled</String>
|
||||
<String key="VolumePaused">Volume while paused</String>
|
||||
<String key="DebugLogVisible">Show log in console</String>
|
||||
<String key="PyScreenPriority">Extra screen update delay</String>
|
||||
<String key="PyScreenPriorityDesc">Minimum time between Python cab screen updates in milliseconds. Higher value = less frequent updates.</String>
|
||||
<String key="PyPriorityNormal">Standard (200 ms)</String>
|
||||
<String key="PyPriorityLower">Slower (400 ms)</String>
|
||||
<String key="PyPriorityLowest">Very slow (800 ms)</String>
|
||||
<String key="PyPriorityIdle">Only when idle (1600 ms)</String>
|
||||
<String key="PyPriorityOff">Off</String>
|
||||
|
||||
<String key="MassBrutto">Gross mass</String>
|
||||
<String key="VisibilityGood">Good</String>
|
||||
@@ -531,7 +583,7 @@
|
||||
<String key="TexWorks">Overhaul:</String>
|
||||
<String key="RevisionFrom">Overhaul from:</String>
|
||||
<String key="RevisionTo">Overhaul to:</String>
|
||||
<String key="CrewLabel">Crew:</String>
|
||||
<String key="CrewLabel">Crew</String>
|
||||
<String key="RuleAdd">Add to rule</String>
|
||||
<String key="RuleNew">— new rule —</String>
|
||||
<String key="RuleRemove">Remove from rule</String>
|
||||
@@ -551,4 +603,18 @@
|
||||
<String key="AdvLogPage">Event log</String>
|
||||
<String key="AdvClearLog">Clear log</String>
|
||||
<String key="ConsistSchedule">Composition</String>
|
||||
|
||||
<!-- Keyboard shortcuts -->
|
||||
<String key="ShortcutsTitle">Keyboard shortcuts</String>
|
||||
<String key="ShortcutsGlobal">General</String>
|
||||
<String key="ShortcutsTabHint">Tab moves between panels, arrow keys inside lists. In the consist strip Tab lands on the selected vehicle.</String>
|
||||
<String key="ShortcutShowHelp">This list</String>
|
||||
<String key="ShortcutFocusSearch">Jump to the vehicle search box</String>
|
||||
<String key="ShortcutAddVehicle">Add the browsed vehicle to the consist</String>
|
||||
<String key="ShortcutSetNumber">Set the wagon number</String>
|
||||
<String key="ShortcutPrevNext">Previous / next vehicle</String>
|
||||
<String key="ShortcutFirstLast">First / last vehicle</String>
|
||||
<String key="ShortcutMember">Pick a car inside a coupled unit</String>
|
||||
<String key="ShortcutMoveVehicle">Move the vehicle along the consist</String>
|
||||
<String key="ShortcutCardMenu">Open the vehicle menu</String>
|
||||
</Language>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<String key="OptionalIncludes">Opcionális elemek</String>
|
||||
<String key="OpenTextureFolder">Textúramappa megnyitása</String>
|
||||
<String key="CopyTextureName">Név másolása</String>
|
||||
<String key="CopyCoupler">Kapcsolókészülék másolása az előzőről</String>
|
||||
<String key="CopyCoupler">Másolás az előzőről</String>
|
||||
<String key="ThermoAmbient">Hűtőfolyadék környezeti hőmérsékleten</String>
|
||||
<String key="TextureInfo">Textúra</String>
|
||||
<String key="TexOperator">Üzemeltető</String>
|
||||
@@ -185,6 +185,9 @@
|
||||
<String key="SwitchNormal">N - fék bekapcsolva</String>
|
||||
<String key="SwitchOff">0 - kikapcsolva, oldva</String>
|
||||
<String key="SwitchOff10">1 - kikapcsolva, oldva (10%)</String>
|
||||
<String key="SwitchEmergencyTest"><> - kikapcsolva gyorsfékpróbához</String>
|
||||
<String key="SwitchAgonal">X - elhasználódott (20% ki, véletlen)</String>
|
||||
<String key="SwitchEmptied">E - kiürítve (vezetékek 0-n)</String>
|
||||
|
||||
<!-- Damage (wheel parameters) -->
|
||||
<String key="Damage">Sérülés</String>
|
||||
@@ -194,15 +197,24 @@
|
||||
<String key="DamageFlatnessProb">Laposodás valószínűsége (%)</String>
|
||||
<String key="LoadType">Rakomány típusa</String>
|
||||
<String key="LoadCount">Mennyiség</String>
|
||||
<String key="LoadUnitPieces">db</String>
|
||||
<String key="LoadUnitTons">t</String>
|
||||
<String key="LoadUnitGeneric">egys.</String>
|
||||
<String key="LoadHint">A jármű megrakásához adjon meg 0-nál nagyobb mennyiséget.</String>
|
||||
<String key="LoadNotLoadable">Ez a jármű nem fogad rakományt.</String>
|
||||
<String key="LoadPantState">Felemelt áramszedő induláskor</String>
|
||||
<String key="LoadPantStateHint">A mennyiség az áramszedőt választja ki: 1 — A, 2 — B, 3 — mindkettő.</String>
|
||||
<String key="Pantograph">Áramszedő</String>
|
||||
<String key="PantStateNone">Egyik sem</String>
|
||||
<String key="PantStateBoth">Mindkettő</String>
|
||||
<String key="WagonNumber">Kocsiszám</String>
|
||||
<String key="LoadCopyPrev">Rakomány másolása az előző járműről</String>
|
||||
<String key="LoadMax">Legnagyobb lehetséges rakomány</String>
|
||||
<String key="LoadPrevShort">Az előzőről</String>
|
||||
<String key="LoadToAllShort">A következőkre</String>
|
||||
<String key="LoadMaxShort">Legnagyobb</String>
|
||||
<String key="ConsistLoadTools">A teljes szerelvény rakománya</String>
|
||||
<String key="ConsistRandomType">Véletlen rakomány</String>
|
||||
<String key="ConsistRandomType">Véletlen típus</String>
|
||||
<String key="ConsistMaxAmount">Legnagyobb mennyiség</String>
|
||||
<String key="ConsistRandomAmount">Véletlen mennyiség</String>
|
||||
<String key="Set">Beállítás</String>
|
||||
@@ -252,6 +264,7 @@
|
||||
<String key="CouplingUnitRear">Az egység végén lévő kapcsolókészülék</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="AddVehicle">Hozzáadás</String>
|
||||
<String key="TipAddVehicle">Jármű hozzáadása a szerelvényhez</String>
|
||||
<String key="TipAddUnit">A teljes egység hozzáadása a szerelvényhez</String>
|
||||
@@ -278,7 +291,7 @@
|
||||
<String key="Fullscreen">Teljes képernyő</String>
|
||||
<String key="PauseSimInactive">Szimuláció szüneteltetése inaktív ablaknál</String>
|
||||
<String key="PauseSimStart">Szüneteltetés indításkor</String>
|
||||
<String key="CursorSensitivity">Kurzorérzékenység:</String>
|
||||
<String key="CursorSensitivity">Kurzorérzékenység</String>
|
||||
<String key="MouseHorInvert">Egér vízszintes megfordítása</String>
|
||||
<String key="MouseVertInvert">Egér függőleges megfordítása</String>
|
||||
|
||||
@@ -309,7 +322,7 @@
|
||||
<String key="UartRadioChannel">Rádiócsatorna</String>
|
||||
|
||||
<String key="Others">Egyéb</String>
|
||||
<String key="SelectEXE">Exe kiválasztása:</String>
|
||||
<String key="SelectEXE">Exe kiválasztása</String>
|
||||
<String key="SelectEXEAuto">Automatikus kiválasztás</String>
|
||||
<String key="DebugMode">Hibakeresési mód (teszt mód)</String>
|
||||
<String key="VirtualShunting">Virtuális tolatás</String>
|
||||
@@ -324,12 +337,12 @@
|
||||
<String key="REOff">Vizuális réteg kikapcsolása</String>
|
||||
<String key="REExperimental">Kísérleti</String>
|
||||
|
||||
<String key="Resolution">Felbontás:</String>
|
||||
<String key="BufforResolution">Puffer felbontása:</String>
|
||||
<String key="MaxTexResolution">Textúrák legnagyobb felbontása:</String>
|
||||
<String key="MaxTexCabResolution">Vezetőállás-textúrák legnagyobb felbontása:</String>
|
||||
<String key="TexFilteringQuality">Textúraszűrés minősége:</String>
|
||||
<String key="Multisampling">Multisampling:</String>
|
||||
<String key="Resolution">Felbontás</String>
|
||||
<String key="BufforResolution">Puffer felbontása</String>
|
||||
<String key="MaxTexResolution">Textúrák legnagyobb felbontása</String>
|
||||
<String key="MaxTexCabResolution">Vezetőállás-textúrák legnagyobb felbontása</String>
|
||||
<String key="TexFilteringQuality">Textúraszűrés minősége</String>
|
||||
<String key="Multisampling">Multisampling</String>
|
||||
<String key="QualityVeryLow">Nagyon alacsony</String>
|
||||
<String key="QualityLow">Alacsony</String>
|
||||
<String key="QualityNormal">Normál</String>
|
||||
@@ -346,8 +359,8 @@
|
||||
<String key="RangeHigh">Magas</String>
|
||||
<String key="RangeVeryHigh">Nagyon magas</String>
|
||||
<String key="RangeDisabled">Kikapcsolva</String>
|
||||
<String key="DynamicLights">Dinamikus fényforrások:</String>
|
||||
<String key="RenderRange">Megjelenítési távolság:</String>
|
||||
<String key="DynamicLights">Dinamikus fényforrások</String>
|
||||
<String key="RenderRange">Megjelenítési távolság</String>
|
||||
<String key="FpsLimitEnable">Képkockaszám korlátozása</String>
|
||||
<String key="ShadowAngleLimit">Árnyékhossz korlátozása</String>
|
||||
<String key="ShadowLimitNone">Nincs korlát</String>
|
||||
@@ -362,6 +375,7 @@
|
||||
<String key="MotionBlur">Mozgási elmosás</String>
|
||||
<String key="AdditionalShadersEffects">További shader-effektek</String>
|
||||
<String key="ReflectionsCubeMap">Tükröződések cubemapként</String>
|
||||
<String key="SkipPipeline">SkipPipeline</String>
|
||||
<String key="RenderVBO">VBO megjelenítés</String>
|
||||
<String key="RenderShadows">Árnyékok megjelenítése</String>
|
||||
<String key="ReflectionsFramerate">Tükröződések frissítési gyakorisága</String>
|
||||
@@ -419,14 +433,14 @@
|
||||
<String key="IgnoreIrrelevantTrains">Dekorációs szerelvények kihagyása indításkor</String>
|
||||
|
||||
<String key="Starter">Starter</String>
|
||||
<String key="SettingsProfile">Beállítási profil:</String>
|
||||
<String key="SettingsProfile">Beállítási profil</String>
|
||||
<String key="ProfileApply">Alkalmaz</String>
|
||||
<String key="ProfileSaveAs">Mentés másként…</String>
|
||||
<String key="ProfileNamePrompt">Profil neve</String>
|
||||
<String key="AutoCloseStarter">A starter automatikus bezárása</String>
|
||||
<String key="LargeThumbnails">Nagy előnézetek</String>
|
||||
<String key="AutoExpandSceneryTree">Pályafa automatikus kibontása</String>
|
||||
<String key="BatteryDefault">Akkumulátor indításkor:</String>
|
||||
<String key="BatteryDefault">Akkumulátor indításkor</String>
|
||||
<String key="BatteryDefaultScenario">Forgatókönyv szerint</String>
|
||||
<String key="BatteryDefaultOff">Mindig ki</String>
|
||||
<String key="BatteryDefaultOn">Mindig be</String>
|
||||
@@ -455,6 +469,7 @@
|
||||
<String key="Save">Mentés</String>
|
||||
<String key="Reset">Visszaállítás</String>
|
||||
<String key="SettingsSaved">A beállítások elmentve</String>
|
||||
<String key="UnsavedChanges">Nem mentett módosítások</String>
|
||||
|
||||
<!-- Errors / diagnostics -->
|
||||
<String key="FaultTitle">Hiba</String>
|
||||
@@ -482,6 +497,7 @@
|
||||
<String key="GeneralInfo">Általános</String>
|
||||
<String key="Model">Modell</String>
|
||||
<String key="VMax">VMax</String>
|
||||
<String key="Name">Név</String>
|
||||
<String key="Texture">Textúra</String>
|
||||
|
||||
<!-- Scenery tab -->
|
||||
@@ -509,9 +525,9 @@
|
||||
<String key="WarehouseImportFile">Szerelvények hozzáadása fájlból...</String>
|
||||
<String key="StartWithoutSaving">Indítás a beállítások mentése nélkül</String>
|
||||
<String key="StartFreeFly">Indítás jármű nélkül</String>
|
||||
<String key="VolumePaused">Hangerő szünet alatt:</String>
|
||||
<String key="VolumePaused">Hangerő szünet alatt</String>
|
||||
<String key="DebugLogVisible">Szimuláció menetének megjelenítése</String>
|
||||
<String key="PyScreenPriority">Kiegészítő képernyők frissítési gyakorisága:</String>
|
||||
<String key="PyScreenPriority">Kiegészítő képernyők frissítési gyakorisága</String>
|
||||
<String key="PyPriorityNormal">Normál</String>
|
||||
<String key="PyPriorityLower">Alacsony</String>
|
||||
<String key="PyPriorityLowest">Nagyon alacsony</String>
|
||||
@@ -531,7 +547,7 @@
|
||||
<String key="TexWorks">Felújítás:</String>
|
||||
<String key="RevisionFrom">Felújítás ettől:</String>
|
||||
<String key="RevisionTo">Felújítás eddig:</String>
|
||||
<String key="CrewLabel">Személyzet:</String>
|
||||
<String key="CrewLabel">Személyzet</String>
|
||||
<String key="RuleAdd">Hozzáadás a szabályhoz</String>
|
||||
<String key="RuleNew">— új szabály —</String>
|
||||
<String key="RuleRemove">Eltávolítás a szabályból</String>
|
||||
@@ -551,4 +567,18 @@
|
||||
<String key="AdvLogPage">Eseménynapló</String>
|
||||
<String key="AdvClearLog">Napló törlése</String>
|
||||
<String key="ConsistSchedule">Összeállítás</String>
|
||||
|
||||
<!-- Billentyűparancsok -->
|
||||
<String key="ShortcutsTitle">Billentyűparancsok</String>
|
||||
<String key="ShortcutsGlobal">Általános</String>
|
||||
<String key="ShortcutsTabHint">A Tab a panelek között lép, a nyilak a listákon belül. A szerelvénysávban a Tab a kijelölt járműre ugrik.</String>
|
||||
<String key="ShortcutShowHelp">Ez a lista</String>
|
||||
<String key="ShortcutFocusSearch">Ugrás a járműkeresőre</String>
|
||||
<String key="ShortcutAddVehicle">A kiválasztott jármű hozzáadása</String>
|
||||
<String key="ShortcutSetNumber">Kocsiszám beállítása</String>
|
||||
<String key="ShortcutPrevNext">Előző / következő jármű</String>
|
||||
<String key="ShortcutFirstLast">Első / utolsó jármű</String>
|
||||
<String key="ShortcutMember">Tag kiválasztása kapcsolt egységen belül</String>
|
||||
<String key="ShortcutMoveVehicle">Jármű mozgatása a szerelvényben</String>
|
||||
<String key="ShortcutCardMenu">Jármű menüjének megnyitása</String>
|
||||
</Language>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<String key="OptionalIncludes">Elementy opcjonalne</String>
|
||||
<String key="OpenTextureFolder">Otwórz folder tekstury</String>
|
||||
<String key="CopyTextureName">Kopiuj nazwę</String>
|
||||
<String key="CopyCoupler">Kopiuj sprzęg z poprzedniego</String>
|
||||
<String key="CopyCoupler">Kopiuj z poprzedniego</String>
|
||||
<String key="ThermoAmbient">Chłodziwo w temp. otoczenia</String>
|
||||
<String key="TextureInfo">Tekstura</String>
|
||||
<String key="TexOperator">Przewoźnik</String>
|
||||
@@ -89,7 +89,7 @@
|
||||
<String key="SelectedVehicle">Wybrany pojazd</String>
|
||||
<String key="Vehicle">Pojazd</String>
|
||||
<String key="Crew">z obsadą</String>
|
||||
<String key="Reversed">obrócony</String>
|
||||
<String key="Reversed">Obrócony</String>
|
||||
<String key="NoVehicles">Nie znaleziono pojazdów w dynamic/**/textures.txt.</String>
|
||||
<String key="ConsistEmpty">Dodaj pojazdy z bazy po lewej stronie lub wczytaj istniejący skład z pliku scenerii.</String>
|
||||
<String key="TipMoveLeft">Przesuń w lewo</String>
|
||||
@@ -185,6 +185,9 @@
|
||||
<String key="SwitchNormal">N - hamulec włączony</String>
|
||||
<String key="SwitchOff">0 - wyłączony, odluźniony</String>
|
||||
<String key="SwitchOff10">1 - wył., odl. z 10% szansą</String>
|
||||
<String key="SwitchEmergencyTest"><> - wył. na próbę hamowania nagłego</String>
|
||||
<String key="SwitchAgonal">X - agonalny (20% wył., losowo)</String>
|
||||
<String key="SwitchEmptied">E - opróżniony (przewody na 0)</String>
|
||||
|
||||
<!-- Uszkodzenia (parametry kół) -->
|
||||
<String key="Damage">Uszkodzenia</String>
|
||||
@@ -194,15 +197,24 @@
|
||||
<String key="DamageFlatnessProb">Prawdop. podkucia (%)</String>
|
||||
<String key="LoadType">Rodzaj ładunku</String>
|
||||
<String key="LoadCount">Ilość</String>
|
||||
<String key="LoadUnitPieces">szt.</String>
|
||||
<String key="LoadUnitTons">t</String>
|
||||
<String key="LoadUnitGeneric">j.</String>
|
||||
<String key="LoadHint">Ustaw ilość większą od 0, aby załadować pojazd.</String>
|
||||
<String key="LoadNotLoadable">Ten pojazd nie przyjmuje ładunku.</String>
|
||||
<String key="LoadPantState">Pantograf podniesiony na starcie</String>
|
||||
<String key="LoadPantStateHint">Ilość wybiera pantograf: 1 — A, 2 — B, 3 — oba.</String>
|
||||
<String key="Pantograph">Pantograf</String>
|
||||
<String key="PantStateNone">Brak</String>
|
||||
<String key="PantStateBoth">Oba</String>
|
||||
<String key="WagonNumber">Numer wagonu</String>
|
||||
<String key="LoadCopyPrev">Kopiuj ładunek z poprzedniego pojazdu</String>
|
||||
<String key="LoadMax">Maksymalny możliwy ładunek</String>
|
||||
<String key="ConsistLoadTools">Ładunek całego składu</String>
|
||||
<String key="ConsistRandomType">Losowy ładunek</String>
|
||||
<String key="LoadPrevShort">Z poprzedniego</String>
|
||||
<String key="LoadToAllShort">Do następnych</String>
|
||||
<String key="LoadMaxShort">Maksymalny</String>
|
||||
<String key="ConsistLoadTools">Ładunki zestawienia</String>
|
||||
<String key="ConsistRandomType">Losowy rodzaj</String>
|
||||
<String key="ConsistMaxAmount">Maksymalna ilość</String>
|
||||
<String key="ConsistRandomAmount">Losowa ilość</String>
|
||||
<String key="Set">Ustaw</String>
|
||||
@@ -252,6 +264,7 @@
|
||||
<String key="CouplingUnitRear">Sprzęg na końcu zespołu</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="AddVehicle">Dodaj</String>
|
||||
<String key="TipAddVehicle">Dodaj ten pojazd do składu</String>
|
||||
<String key="TipAddUnit">Dodaj cały zespół do składu</String>
|
||||
@@ -278,23 +291,26 @@
|
||||
<String key="Fullscreen">Tryb pełnoekranowy</String>
|
||||
<String key="PauseSimInactive">Wstrzymaj symulację, gdy okno nie jest aktywne</String>
|
||||
<String key="PauseSimStart">Pauza po uruchomieniu</String>
|
||||
<String key="CursorSensitivity">Czułość kursora:</String>
|
||||
<String key="CursorSensitivity">Czułość obrotu kamery (oś pozioma)</String>
|
||||
<String key="MouseHorInvert">Inwersja myszy w poziomie</String>
|
||||
<String key="MouseVertInvert">Inwersja myszy w pionie</String>
|
||||
|
||||
<String key="Communication">Komunikacja</String>
|
||||
<String key="Communication">Urządzenia zewnętrzne</String>
|
||||
|
||||
<String key="GamepadSignalIgnore">Ignoruj sygnały z gamepada</String>
|
||||
<String key="GamepadSignalIgnore">Ignoruj sterowanie z gamepada</String>
|
||||
<String key="GamepadSignalIgnoreDesc">Wyłącza obsługę gamepada, nawet gdy jest podłączony.</String>
|
||||
|
||||
<String key="Feedback">Informacja zwrotna</String>
|
||||
<String key="FeedbackDesc">Sposób przekazywania sygnałów kabinowych na zewnętrzny pulpit. Tryby klawiatury migają diodami: (1) Caps Lock = CA lub SHP, Scroll Lock = jazda na oporach; (2) Caps Lock = CA, Scroll Lock = SHP. Pozostałe tryby wymagają interfejsu LPT, PoKeys55 lub portu szeregowego.</String>
|
||||
<String key="FeedbackOff">Wyłączona</String>
|
||||
<String key="FeedbackCASHP">Caps: CA/SHP; ScrollLock: opory rozr.</String>
|
||||
<String key="FeedbackCA">Caps: CA; Scrollock: SHP</String>
|
||||
<String key="FeedbackLPT">LPT</String>
|
||||
<String key="FeedbackPoKeys">PoKeys55</String>
|
||||
<String key="FeedbackSP">Serialport (COM)</String>
|
||||
<String key="FeedbackCASHP">Klawiatura: Caps = CA+SHP, Scroll = opory</String>
|
||||
<String key="FeedbackCA">Klawiatura: Caps = CA, Scroll = SHP</String>
|
||||
<String key="FeedbackLPT">LPT (inpout32)</String>
|
||||
<String key="FeedbackPoKeys">PoKeys55 (USB)</String>
|
||||
<String key="FeedbackSP">COM / port szeregowy</String>
|
||||
|
||||
<String key="FeedbackPort">Adres portu LPT</String>
|
||||
<String key="FeedbackPortDesc">Adres portu LPT używanego przez wybrany tryb informacji zwrotnej.</String>
|
||||
|
||||
<String key="UartSection">Konfiguracja UART</String>
|
||||
<String key="UartEnable">Włącz komunikację UART</String>
|
||||
@@ -309,27 +325,34 @@
|
||||
<String key="UartRadioChannel">Kanał radiotelefonu</String>
|
||||
|
||||
<String key="Others">Inne</String>
|
||||
<String key="SelectEXE">Wybór exe:</String>
|
||||
<String key="SelectEXE">Wybór exe</String>
|
||||
<String key="SelectEXEAuto">Wybierz automatycznie</String>
|
||||
<String key="DebugMode">Tryb testowy (debugmode)</String>
|
||||
<String key="DebugMode">Tryb testowy</String>
|
||||
<String key="DebugModeDesc">Odblokowuje menu debug, dodatkowe panele i skróty testowe.</String>
|
||||
<String key="VirtualShunting">Wirtualny manewrowy</String>
|
||||
<String key="VirtualShuntingDesc">Włącza asystenta AI do sprzęgania, rozprzęgania i prostych manewrów, gdy pojazd stoi.</String>
|
||||
<String key="LogMissingVehicleFiles">Zapisuj w logu brakujące pliki pojazdów podczas eksportu</String>
|
||||
<String key="LogMissingVehicleFilesDesc">Zapisuje w logu pojazdy, których starter nie znalazł.</String>
|
||||
|
||||
<String key="Graphic">Grafika</String>
|
||||
<String key="RenderEngine">Silnik renderowania</String>
|
||||
<String key="RenderEngineDesc">Wybór ścieżki renderowania OpenGL. 'Uproszczony' i 'Stary' to starszy renderer.</String>
|
||||
<String key="REFull">Pełny</String>
|
||||
<String key="REOld">Stary</String>
|
||||
<String key="RESimpShader">Uproszczony shaderowy</String>
|
||||
<String key="RESimpShader">Uproszczony (legacy)</String>
|
||||
<String key="RESimp">Uproszczony</String>
|
||||
<String key="REOff">Wyłącz warstwę wizualną</String>
|
||||
<String key="REExperimental">Eksperymentalny</String>
|
||||
|
||||
<String key="Resolution">Rozdzielczość:</String>
|
||||
<String key="BufforResolution">Rozdzielczość bufora:</String>
|
||||
<String key="MaxTexResolution">Maksymalna rozdzielczość tekstur:</String>
|
||||
<String key="MaxTexCabResolution">Maksymalna rozdzielczość tekstur kabiny:</String>
|
||||
<String key="TexFilteringQuality">Jakość filtrowania tekstur:</String>
|
||||
<String key="Multisampling">Multisampling:</String>
|
||||
<String key="Resolution">Rozdzielczość</String>
|
||||
<String key="BufforResolution">Rozdzielczość bufora</String>
|
||||
<String key="BufforResolutionDesc">Skaluje wewnętrzny bufor renderowania względem rozdzielczości okna.</String>
|
||||
<String key="MaxTexResolution">Maksymalna rozdzielczość tekstur</String>
|
||||
<String key="MaxTexCabResolution">Maksymalna rozdzielczość tekstur kabiny</String>
|
||||
<String key="TexFilteringQuality">Jakość filtrowania tekstur</String>
|
||||
<String key="TexFilteringQualityDesc">Poziom anizotropowego filtrowania tekstur.</String>
|
||||
<String key="Multisampling">Multisampling</String>
|
||||
<String key="MultisamplingDesc">Wygładzanie krawędzi obiektów.</String>
|
||||
<String key="QualityVeryLow">Bardzo niska</String>
|
||||
<String key="QualityLow">Niska</String>
|
||||
<String key="QualityNormal">Normalna</String>
|
||||
@@ -346,15 +369,20 @@
|
||||
<String key="RangeHigh">Wysoki</String>
|
||||
<String key="RangeVeryHigh">Bardzo wysoki</String>
|
||||
<String key="RangeDisabled">Wyłączone</String>
|
||||
<String key="DynamicLights">Dynamiczne źródła światła:</String>
|
||||
<String key="RenderRange">Zasięg renderowania:</String>
|
||||
<String key="DynamicLights">Dynamiczne źródła światła</String>
|
||||
<String key="DynamicLightsDesc">Maksymalna liczba jednoczesnych źródeł światła dynamicznego.</String>
|
||||
<String key="RenderRange">Zasięg renderowania</String>
|
||||
<String key="RenderRangeDesc">Maksymalny mnożnik zasięgu rysowania; silnik zmniejsza go automatycznie przy niskim FPS.</String>
|
||||
<String key="FpsLimitEnable">Ogranicz liczbę klatek na sekundę</String>
|
||||
<String key="ShadowAngleLimit">Limit długości cienia</String>
|
||||
<String key="ShadowAngleLimitDesc">Skraca długie cienie rzucane przez nisko stojące słońce. Wyższe wartości ucinają je wcześniej.</String>
|
||||
<String key="ShadowLimitNone">Bez ograniczeń</String>
|
||||
<String key="ShadowLimitShort">Skrócone</String>
|
||||
<String key="FullscreenWindowed">Automatyczna rozdzielczość (pełny ekran bez obramowania)</String>
|
||||
<String key="RenderAngleVulkan">Użyj renderera ANGLE Vulkan</String>
|
||||
<String key="RenderAngleVulkanDesc">Uruchamia renderer ANGLE na Vulkanie zamiast domyślnego backendu. Przydatne, gdy OpenGL sprawia problemy.</String>
|
||||
<String key="VSync">VSync</String>
|
||||
<String key="VSyncDesc">Synchronizacja klatek z odświeżaniem monitora.</String>
|
||||
<String key="SmokeDisplay">Wyświetlanie dymu</String>
|
||||
<String key="SmokeParticlesMultiplier">Mnożnik ilości cząsteczek dymu</String>
|
||||
|
||||
@@ -362,48 +390,68 @@
|
||||
<String key="MotionBlur">Rozmycie podczas ruchu</String>
|
||||
<String key="AdditionalShadersEffects">Dodatkowe efekty shaderów</String>
|
||||
<String key="ReflectionsCubeMap">Odbicia jako cubemapa</String>
|
||||
<String key="ReflectionsCubeMapDesc">Renderuje odbicia do mapy sześciennej, dzięki czemu powierzchnie odbijają otoczenie ze wszystkich stron.</String>
|
||||
<String key="SkipPipeline">Uproszczony odczyt głębi (zgodność)</String>
|
||||
<String key="SkipPipelineDesc">Alternatywna ścieżka odczytu bufora głębi przy MSAA; włącz przy problemach z kompatybilnością karty graficznej.</String>
|
||||
<String key="RenderVBO">Renderowanie VBO</String>
|
||||
<String key="RenderVBODesc">Trzyma geometrię w buforach karty graficznej. Wyłącz tylko przy problemach ze starszymi kartami.</String>
|
||||
<String key="RenderShadows">Renderowanie cieni</String>
|
||||
<String key="ReflectionsFramerate">Odświeżanie odbić</String>
|
||||
<String key="ShadowsResolution">Rozdzielczość cieni</String>
|
||||
<String key="ShaderRange">Zasięg cieni</String>
|
||||
<String key="CabShadowsRange">Zasięg źródeł cieni w kabinie</String>
|
||||
<String key="CabShadowsRangeDesc">Odległość, z jakiej źródła światła nadal rzucają cień w kabinie. Zero wyłącza cienie w kabinie.</String>
|
||||
|
||||
<String key="ShadowDisplay">Wyświetlanie cieni</String>
|
||||
<String key="ShadowDisplayDesc">Określa, które obiekty rzucają cień według priorytetu materiału. Niższa wartość = mniej cieni, wyższa wydajność.</String>
|
||||
<String key="CSROnlyImportant">Tylko ważne</String>
|
||||
<String key="CSRLimited">Ograniczone</String>
|
||||
<String key="CSRAll">Wszystkie</String>
|
||||
|
||||
<String key="ReflectionsDetails">Szczegółowość odbić</String>
|
||||
<String key="ReflectionsDetailsDesc">Jakie obiekty są renderowane w odbiciach cubemap.</String>
|
||||
<String key="RDTerrain">Tylko geometria terenu</String>
|
||||
<String key="RDTerrainModels">Geometria terenu i modele statyczne</String>
|
||||
<String key="RDTerrainModelsVehicles">Geometria terenu, modele statyczne i pojazdy</String>
|
||||
|
||||
<String key="FieldOfView">Kąt widzenia</String>
|
||||
<String key="FieldOfViewDesc">Pionowy kąt widzenia kamery w stopniach (15–75).</String>
|
||||
<String key="RenderScreens">Renderowanie ekranów komputerów pokładowych</String>
|
||||
<String key="RenderScreensDesc">Włącza ekrany kabinowe obsługiwane przez skrypty Python.</String>
|
||||
<String key="RenderScreensThread">Renderowanie ekranów w osobnym wątku</String>
|
||||
|
||||
<String key="Physics">Fizyka</String>
|
||||
|
||||
<String key="TrackCurvesQuality">Jakość łuków torów</String>
|
||||
<String key="TrackCurvesQualityDesc">Gęstość podziału łuków torów. Wyższa wartość = gładsze łuki.</String>
|
||||
<String key="PhysicsAccuracyIncreased">Zwiększona dokładność fizyki</String>
|
||||
<String key="PhysicsAccuracyIncreasedDesc">Uruchamia pełny model fizyki z mniejszym krokiem czasowym.</String>
|
||||
<String key="BreakingPantographPossibility">Możliwość złamania pantografu</String>
|
||||
<String key="BreakingPantographPossibilityDesc">Wyłączenie chroni pantograf przed uszkodzeniem przy błędnej geometrii sieci.</String>
|
||||
<String key="PowerOverheadLineOnly">Zasilanie tylko pod siecią trakcyjna</String>
|
||||
<String key="SaveSpeedometerTapes">Zapisuj taśmy z prędkościomierza</String>
|
||||
<String key="PowerOverheadLineOnlyDesc">Gdy wyłączone, podniesiony pantograf zasila pojazd nawet bez kontaktu z drutem.</String>
|
||||
<String key="SaveSpeedometerTapes">Zapisuj log parametrów fizycznych</String>
|
||||
<String key="SaveSpeedometerTapesDesc">Zapisuje szczegółowy log jazdy do folderu physicslog/.</String>
|
||||
<String key="SaveSimLogs">Zapisuj log z symulacji</String>
|
||||
<String key="KeepPrevLogs">Zachowuj poprzednie logi</String>
|
||||
<String key="DisplaySimulation">Wyświetlaj symulację</String>
|
||||
<String key="KeepPrevLogsDesc">Tworzy osobny plik logu dla każdej scenerii zamiast nadpisywać errors.txt.</String>
|
||||
<String key="DisplaySimulation">Renderuj obraz 3D</String>
|
||||
<String key="DisplaySimulationDesc">Wyłączenie pomija rysowanie scenerii (czarny ekran), ale symulacja nadal działa.</String>
|
||||
<String key="CrashDamage">Uszkodzenia przy zderzeniu</String>
|
||||
<String key="CrashDamageDesc">Uszkodzenia sprzęgów i składów przy kolizjach i gwałtownym naciągu.</String>
|
||||
<String key="Friction">Mnożnik tarcia</String>
|
||||
<String key="BrakeStep">Czułość zaworu hamulcowego</String>
|
||||
<String key="BrakeSpeed">Czułość zaworu FV4a</String>
|
||||
<String key="FrictionDesc">Mnoży tarcie koła o szynę. 1,00 nie zmienia niczego.</String>
|
||||
<String key="BrakeStep">Krok nastawy hamulca zespolonego (Num3/Num9)</String>
|
||||
<String key="BrakeStepDesc">Mnoży wielkość pojedynczego kroku kranu hamulca zespolonego sterowanego klawiszami Num3 i Num9. 1,00 nie zmienia niczego.</String>
|
||||
<String key="BrakeSpeed">Szybkość zaworu FV4a</String>
|
||||
<String key="BrakeSpeedDesc">Mnoży szybkość przesuwania kranu hamulca lokomotywy (FV4a) i hamulca lokalnego. 1,00 nie zmienia niczego.</String>
|
||||
|
||||
<String key="Sounds">Dżwięk</String>
|
||||
<String key="Sounds">Dźwięk</String>
|
||||
<String key="EnableSounds">Włącz dźwięk</String>
|
||||
<String key="Volume">Głośność</String>
|
||||
<String key="RadiophoneVolume">Głośność radiotelefonu</String>
|
||||
<String key="VehiclesVolume">Głośność pojazdów</String>
|
||||
<String key="PositionedSoundsVolume">Głośność dżwięków pozycjonowanych</String>
|
||||
<String key="PositionedSoundsVolume">Głośność dźwięków pozycjonowanych</String>
|
||||
<String key="AmbientVolume">Głośność otoczenia</String>
|
||||
|
||||
<String key="Advanced">Zaawansowane</String>
|
||||
@@ -419,14 +467,15 @@
|
||||
<String key="IgnoreIrrelevantTrains">Pomijaj składy dekoracyjne przy starcie</String>
|
||||
|
||||
<String key="Starter">Starter</String>
|
||||
<String key="SettingsProfile">Profil ustawień:</String>
|
||||
<String key="SettingsProfile">Profil ustawień</String>
|
||||
<String key="ProfileApply">Zastosuj</String>
|
||||
<String key="ProfileSaveAs">Zapisz jako…</String>
|
||||
<String key="ProfileNamePrompt">Nazwa profilu</String>
|
||||
<String key="AutoCloseStarter">Zamknij starter automatycznie</String>
|
||||
<String key="LargeThumbnails">Duże miniaturki</String>
|
||||
<String key="AutoExpandSceneryTree">Automatycznie rozwijanie drzewka scenerii</String>
|
||||
<String key="BatteryDefault">Akumulator przy starcie:</String>
|
||||
<String key="BatteryDefault">Akumulator przy starcie</String>
|
||||
<String key="BatteryDefaultDesc">Stan akumulatora przy starcie symulacji, o ile scenariusz nie zdecyduje inaczej.</String>
|
||||
<String key="BatteryDefaultScenario">Domyślnie ze scenerii</String>
|
||||
<String key="BatteryDefaultOff">Zawsze wyłączony</String>
|
||||
<String key="BatteryDefaultOn">Zawsze włączony</String>
|
||||
@@ -455,6 +504,7 @@
|
||||
<String key="Save">Zapisz</String>
|
||||
<String key="Reset">Przywróć</String>
|
||||
<String key="SettingsSaved">Zapisano ustawienia</String>
|
||||
<String key="UnsavedChanges">Niezapisane zmiany</String>
|
||||
|
||||
<!-- Errors / diagnostics -->
|
||||
<String key="FaultTitle">Błąd</String>
|
||||
@@ -482,6 +532,7 @@
|
||||
<String key="GeneralInfo">Ogólne</String>
|
||||
<String key="Model">Model</String>
|
||||
<String key="VMax">VMax</String>
|
||||
<String key="Name">Nazwa</String>
|
||||
<String key="Texture">Tekstura</String>
|
||||
|
||||
<!-- Scenery tab -->
|
||||
@@ -509,13 +560,14 @@
|
||||
<String key="WarehouseImportFile">Dodaj składy z pliku...</String>
|
||||
<String key="StartWithoutSaving">Uruchom bez zapisu ustawień</String>
|
||||
<String key="StartFreeFly">Uruchom bez pojazdu</String>
|
||||
<String key="VolumePaused">Głośność podczas pauzy:</String>
|
||||
<String key="DebugLogVisible">Wyświetlaj przebieg symulacji</String>
|
||||
<String key="PyScreenPriority">Częstotliwość odświeżania ekranów dodatkowych:</String>
|
||||
<String key="PyPriorityNormal">Standardowa</String>
|
||||
<String key="PyPriorityLower">Niska</String>
|
||||
<String key="PyPriorityLowest">Bardzo niska</String>
|
||||
<String key="PyPriorityIdle">Brak</String>
|
||||
<String key="VolumePaused">Głośność podczas pauzy</String>
|
||||
<String key="DebugLogVisible">Wyświetlaj log w konsoli</String>
|
||||
<String key="PyScreenPriority">Opóźnienie odświeżania ekranów dodatkowych</String>
|
||||
<String key="PyScreenPriorityDesc">Minimalny czas między aktualizacjami ekranów Python w kabinach (ms). Wyższa wartość = rzadsze odświeżanie.</String>
|
||||
<String key="PyPriorityNormal">Standardowe (200 ms)</String>
|
||||
<String key="PyPriorityLower">Wolniejsze (400 ms)</String>
|
||||
<String key="PyPriorityLowest">Bardzo wolne (800 ms)</String>
|
||||
<String key="PyPriorityIdle">Tylko gdy wolne (1600 ms)</String>
|
||||
<String key="PyPriorityOff">Wyłączona</String>
|
||||
|
||||
<String key="MassBrutto">Masa brutto</String>
|
||||
@@ -525,13 +577,13 @@
|
||||
<String key="DriverHead">Maszynista (kabina A)</String>
|
||||
<String key="DriverRear">Maszynista (kabina B)</String>
|
||||
<String key="DriverPassenger">Pasażer</String>
|
||||
<String key="DriverNobody">Nikt</String>
|
||||
<String key="DriverNobody">Brak</String>
|
||||
<String key="RemoveAllTrains">Usuń wszystkie składy ze scenerii</String>
|
||||
<String key="RemoveAllTrainsConfirm">Usunąć pojazdy ze wszystkich składów na tej scenerii?</String>
|
||||
<String key="TexWorks">Rewizja:</String>
|
||||
<String key="RevisionFrom">Rewizja od:</String>
|
||||
<String key="RevisionTo">Rewizja do:</String>
|
||||
<String key="CrewLabel">Obsada:</String>
|
||||
<String key="CrewLabel">Obsada</String>
|
||||
<String key="RuleAdd">Dodaj do reguły</String>
|
||||
<String key="RuleNew">— nowa reguła —</String>
|
||||
<String key="RuleRemove">Usuń z reguły</String>
|
||||
@@ -551,4 +603,18 @@
|
||||
<String key="AdvLogPage">Dziennik zdarzeń</String>
|
||||
<String key="AdvClearLog">Wyczyść log</String>
|
||||
<String key="ConsistSchedule">Zestawienie</String>
|
||||
|
||||
<!-- Skróty klawiaturowe -->
|
||||
<String key="ShortcutsTitle">Skróty klawiaturowe</String>
|
||||
<String key="ShortcutsGlobal">Ogólne</String>
|
||||
<String key="ShortcutsTabHint">Tab przechodzi między panelami, strzałki działają wewnątrz list. W pasku składu Tab wchodzi na zaznaczony pojazd.</String>
|
||||
<String key="ShortcutShowHelp">Ta lista</String>
|
||||
<String key="ShortcutFocusSearch">Przejdź do wyszukiwarki pojazdów</String>
|
||||
<String key="ShortcutAddVehicle">Dodaj wybrany pojazd do składu</String>
|
||||
<String key="ShortcutSetNumber">Ustaw numer wagonu</String>
|
||||
<String key="ShortcutPrevNext">Poprzedni / następny pojazd</String>
|
||||
<String key="ShortcutFirstLast">Pierwszy / ostatni pojazd</String>
|
||||
<String key="ShortcutMember">Wybierz człon w zespolonej jednostce</String>
|
||||
<String key="ShortcutMoveVehicle">Przesuń pojazd w składzie</String>
|
||||
<String key="ShortcutCardMenu">Otwórz menu pojazdu</String>
|
||||
</Language>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<String key="OptionalIncludes">Дополнительные элементы</String>
|
||||
<String key="OpenTextureFolder">Открыть папку с текстурами</String>
|
||||
<String key="CopyTextureName">Копировать название</String>
|
||||
<String key="CopyCoupler">Копировать сцепку с предыдущего</String>
|
||||
<String key="CopyCoupler">Копировать с предыдущего</String>
|
||||
<String key="ThermoAmbient">Охлаждающая жидкость при температуре среды</String>
|
||||
<String key="TextureInfo">Текстура</String>
|
||||
<String key="TexOperator">Перевозчик</String>
|
||||
@@ -185,6 +185,9 @@
|
||||
<String key="SwitchNormal">N - тормоз включён</String>
|
||||
<String key="SwitchOff">0 - выключен, отпущен</String>
|
||||
<String key="SwitchOff10">1 - выключен, отпущен (10%)</String>
|
||||
<String key="SwitchEmergencyTest"><> - выкл. на пробу экстренного торможения</String>
|
||||
<String key="SwitchAgonal">X - изношенный (20% выкл., случайно)</String>
|
||||
<String key="SwitchEmptied">E - опорожнён (магистрали на 0)</String>
|
||||
|
||||
<!-- Damage (wheel parameters) -->
|
||||
<String key="Damage">Повреждения</String>
|
||||
@@ -194,15 +197,24 @@
|
||||
<String key="DamageFlatnessProb">Вероятность ползуна (%)</String>
|
||||
<String key="LoadType">Вид груза</String>
|
||||
<String key="LoadCount">Количество</String>
|
||||
<String key="LoadUnitPieces">шт</String>
|
||||
<String key="LoadUnitTons">т</String>
|
||||
<String key="LoadUnitGeneric">ед.</String>
|
||||
<String key="LoadHint">Задайте количество больше 0, чтобы загрузить единицу.</String>
|
||||
<String key="LoadNotLoadable">Эта единица не принимает груз.</String>
|
||||
<String key="LoadPantState">Поднятый пантограф на старте</String>
|
||||
<String key="LoadPantStateHint">Количество выбирает пантограф: 1 — A, 2 — B, 3 — оба.</String>
|
||||
<String key="Pantograph">Пантограф</String>
|
||||
<String key="PantStateNone">Нет</String>
|
||||
<String key="PantStateBoth">Оба</String>
|
||||
<String key="WagonNumber">Номер вагона</String>
|
||||
<String key="LoadCopyPrev">Копировать груз с предыдущей единицы</String>
|
||||
<String key="LoadMax">Максимально возможный груз</String>
|
||||
<String key="LoadPrevShort">С предыдущей</String>
|
||||
<String key="LoadToAllShort">На следующие</String>
|
||||
<String key="LoadMaxShort">Максимум</String>
|
||||
<String key="ConsistLoadTools">Груз всего состава</String>
|
||||
<String key="ConsistRandomType">Случайный груз</String>
|
||||
<String key="ConsistRandomType">Случайный тип</String>
|
||||
<String key="ConsistMaxAmount">Максимальное количество</String>
|
||||
<String key="ConsistRandomAmount">Случайное количество</String>
|
||||
<String key="Set">Задать</String>
|
||||
@@ -252,6 +264,7 @@
|
||||
<String key="CouplingUnitRear">Сцепка в конце состава</String>
|
||||
<String key="Join">Соединить</String>
|
||||
<String key="TipJoin">Соединить этот вагон со следующим в один состав</String>
|
||||
<String key="TipReorder">Перетащите для изменения порядка</String>
|
||||
<String key="AddVehicle">Добавить</String>
|
||||
<String key="TipAddVehicle">Добавить эту единицу в состав</String>
|
||||
<String key="TipAddUnit">Добавить весь сцеп в состав</String>
|
||||
@@ -278,7 +291,7 @@
|
||||
<String key="Fullscreen">Полный экран</String>
|
||||
<String key="PauseSimInactive">Приостанавливать симуляцию при неактивном окне</String>
|
||||
<String key="PauseSimStart">Пауза при запуске</String>
|
||||
<String key="CursorSensitivity">Чувствительность курсора:</String>
|
||||
<String key="CursorSensitivity">Чувствительность курсора</String>
|
||||
<String key="MouseHorInvert">Инвертировать мышь по горизонтали</String>
|
||||
<String key="MouseVertInvert">Инвертировать мышь по вертикали</String>
|
||||
|
||||
@@ -309,7 +322,7 @@
|
||||
<String key="UartRadioChannel">Канал радио</String>
|
||||
|
||||
<String key="Others">Прочее</String>
|
||||
<String key="SelectEXE">Выбрать exe:</String>
|
||||
<String key="SelectEXE">Выбрать exe</String>
|
||||
<String key="SelectEXEAuto">Выбрать автоматически</String>
|
||||
<String key="DebugMode">Режим отладки (тестовый режим)</String>
|
||||
<String key="VirtualShunting">Виртуальный манёвр</String>
|
||||
@@ -324,12 +337,12 @@
|
||||
<String key="REOff">Отключить визуальный слой</String>
|
||||
<String key="REExperimental">Экспериментальный</String>
|
||||
|
||||
<String key="Resolution">Разрешение:</String>
|
||||
<String key="BufforResolution">Разрешение буфера:</String>
|
||||
<String key="MaxTexResolution">Максимальное разрешение текстур:</String>
|
||||
<String key="MaxTexCabResolution">Максимальное разрешение текстур кабины:</String>
|
||||
<String key="TexFilteringQuality">Качество фильтрации текстур:</String>
|
||||
<String key="Multisampling">Multisampling:</String>
|
||||
<String key="Resolution">Разрешение</String>
|
||||
<String key="BufforResolution">Разрешение буфера</String>
|
||||
<String key="MaxTexResolution">Максимальное разрешение текстур</String>
|
||||
<String key="MaxTexCabResolution">Максимальное разрешение текстур кабины</String>
|
||||
<String key="TexFilteringQuality">Качество фильтрации текстур</String>
|
||||
<String key="Multisampling">Multisampling</String>
|
||||
<String key="QualityVeryLow">Очень низкое</String>
|
||||
<String key="QualityLow">Низкое</String>
|
||||
<String key="QualityNormal">Нормальное</String>
|
||||
@@ -346,8 +359,8 @@
|
||||
<String key="RangeHigh">Высокий</String>
|
||||
<String key="RangeVeryHigh">Очень высокий</String>
|
||||
<String key="RangeDisabled">Отключено</String>
|
||||
<String key="DynamicLights">Динамические источники света:</String>
|
||||
<String key="RenderRange">Дальность отрисовки:</String>
|
||||
<String key="DynamicLights">Динамические источники света</String>
|
||||
<String key="RenderRange">Дальность отрисовки</String>
|
||||
<String key="FpsLimitEnable">Ограничить частоту кадров</String>
|
||||
<String key="ShadowAngleLimit">Ограничение длины теней</String>
|
||||
<String key="ShadowLimitNone">Без ограничений</String>
|
||||
@@ -362,6 +375,7 @@
|
||||
<String key="MotionBlur">Размытие в движении</String>
|
||||
<String key="AdditionalShadersEffects">Дополнительные эффекты шейдеров</String>
|
||||
<String key="ReflectionsCubeMap">Отражения как кубическая карта</String>
|
||||
<String key="SkipPipeline">SkipPipeline</String>
|
||||
<String key="RenderVBO">Отрисовка VBO</String>
|
||||
<String key="RenderShadows">Отрисовывать тени</String>
|
||||
<String key="ReflectionsFramerate">Частота обновления отражений</String>
|
||||
@@ -419,14 +433,14 @@
|
||||
<String key="IgnoreIrrelevantTrains">Пропускать декоративные составы при запуске</String>
|
||||
|
||||
<String key="Starter">Starter</String>
|
||||
<String key="SettingsProfile">Профиль настроек:</String>
|
||||
<String key="SettingsProfile">Профиль настроек</String>
|
||||
<String key="ProfileApply">Применить</String>
|
||||
<String key="ProfileSaveAs">Сохранить как…</String>
|
||||
<String key="ProfileNamePrompt">Название профиля</String>
|
||||
<String key="AutoCloseStarter">Автоматически закрывать стартер</String>
|
||||
<String key="LargeThumbnails">Крупные миниатюры</String>
|
||||
<String key="AutoExpandSceneryTree">Автоматически разворачивать дерево сценерий</String>
|
||||
<String key="BatteryDefault">Батарея при запуске:</String>
|
||||
<String key="BatteryDefault">Батарея при запуске</String>
|
||||
<String key="BatteryDefaultScenario">По сценарию</String>
|
||||
<String key="BatteryDefaultOff">Всегда выключена</String>
|
||||
<String key="BatteryDefaultOn">Всегда включена</String>
|
||||
@@ -455,6 +469,7 @@
|
||||
<String key="Save">Сохранить</String>
|
||||
<String key="Reset">Сбросить</String>
|
||||
<String key="SettingsSaved">Настройки сохранены</String>
|
||||
<String key="UnsavedChanges">Несохранённые изменения</String>
|
||||
|
||||
<!-- Errors / diagnostics -->
|
||||
<String key="FaultTitle">Ошибка</String>
|
||||
@@ -482,6 +497,7 @@
|
||||
<String key="GeneralInfo">Общие</String>
|
||||
<String key="Model">Модель</String>
|
||||
<String key="VMax">VMax</String>
|
||||
<String key="Name">Имя</String>
|
||||
<String key="Texture">Текстура</String>
|
||||
|
||||
<!-- Scenery tab -->
|
||||
@@ -509,9 +525,9 @@
|
||||
<String key="WarehouseImportFile">Добавить составы из файла...</String>
|
||||
<String key="StartWithoutSaving">Запустить без сохранения настроек</String>
|
||||
<String key="StartFreeFly">Запустить без транспорта</String>
|
||||
<String key="VolumePaused">Громкость на паузе:</String>
|
||||
<String key="VolumePaused">Громкость на паузе</String>
|
||||
<String key="DebugLogVisible">Показывать ход симуляции</String>
|
||||
<String key="PyScreenPriority">Частота обновления дополнительных экранов:</String>
|
||||
<String key="PyScreenPriority">Частота обновления дополнительных экранов</String>
|
||||
<String key="PyPriorityNormal">Стандартная</String>
|
||||
<String key="PyPriorityLower">Низкая</String>
|
||||
<String key="PyPriorityLowest">Очень низкая</String>
|
||||
@@ -531,7 +547,7 @@
|
||||
<String key="TexWorks">Ревизия:</String>
|
||||
<String key="RevisionFrom">Ревизия с:</String>
|
||||
<String key="RevisionTo">Ревизия по:</String>
|
||||
<String key="CrewLabel">Экипаж:</String>
|
||||
<String key="CrewLabel">Экипаж</String>
|
||||
<String key="RuleAdd">Добавить в правило</String>
|
||||
<String key="RuleNew">— новое правило —</String>
|
||||
<String key="RuleRemove">Удалить из правила</String>
|
||||
@@ -551,4 +567,18 @@
|
||||
<String key="AdvLogPage">Журнал событий</String>
|
||||
<String key="AdvClearLog">Очистить журнал</String>
|
||||
<String key="ConsistSchedule">Состав</String>
|
||||
|
||||
<!-- Сочетания клавиш -->
|
||||
<String key="ShortcutsTitle">Сочетания клавиш</String>
|
||||
<String key="ShortcutsGlobal">Общие</String>
|
||||
<String key="ShortcutsTabHint">Tab переключает панели, стрелки работают внутри списков. В полосе состава Tab переходит на выбранный вагон.</String>
|
||||
<String key="ShortcutShowHelp">Этот список</String>
|
||||
<String key="ShortcutFocusSearch">Перейти к поиску подвижного состава</String>
|
||||
<String key="ShortcutAddVehicle">Добавить выбранный вагон в состав</String>
|
||||
<String key="ShortcutSetNumber">Задать номер вагона</String>
|
||||
<String key="ShortcutPrevNext">Предыдущий / следующий вагон</String>
|
||||
<String key="ShortcutFirstLast">Первый / последний вагон</String>
|
||||
<String key="ShortcutMember">Выбрать секцию в сцепленной единице</String>
|
||||
<String key="ShortcutMoveVehicle">Переместить вагон в составе</String>
|
||||
<String key="ShortcutCardMenu">Открыть меню вагона</String>
|
||||
</Language>
|
||||
|
||||
Reference in New Issue
Block a user