mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-16 23:49:19 +02:00
wider migration from old starter
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
/StarterNG/obj
|
/StarterNG/obj
|
||||||
/.idea
|
/.idea
|
||||||
/.vs
|
/.vs
|
||||||
|
starter/
|
||||||
|
|||||||
48
README.md
Normal file
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# StarterNG
|
||||||
|
|
||||||
|
Launcher for the MaSzyna train simulator, written in C# with Avalonia.
|
||||||
|
|
||||||
|
## Building a native (NativeAOT) build
|
||||||
|
|
||||||
|
The project publishes as a self-contained NativeAOT binary — no .NET runtime is
|
||||||
|
needed on the target machine. The commands below are the same ones CI runs, see
|
||||||
|
[`.github/workflows/dotnet.yml`](.github/workflows/dotnet.yml).
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- .NET SDK 9
|
||||||
|
- Linux: a native toolchain and zlib headers — `sudo apt install clang zlib1g-dev`
|
||||||
|
(Debian/Ubuntu) or `sudo dnf install clang zlib-devel` (Fedora)
|
||||||
|
- Windows: the Visual Studio C++ build tools (Desktop development with C++)
|
||||||
|
|
||||||
|
Restore for the target runtime first, then publish:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet restore StarterNG/StarterNG.csproj -r linux-x64
|
||||||
|
|
||||||
|
dotnet publish StarterNG/StarterNG.csproj \
|
||||||
|
-c Release -r linux-x64 --self-contained true --no-restore \
|
||||||
|
-p:PublishAot=true -p:PublishTrimmed=true \
|
||||||
|
-o publish/linux-x64
|
||||||
|
```
|
||||||
|
|
||||||
|
For a Windows build swap both `linux-x64` occurrences for `win-x64` (and the
|
||||||
|
line continuations for PowerShell backticks). NativeAOT does not cross-compile,
|
||||||
|
so the Windows package has to be built on Windows and the Linux one on Linux —
|
||||||
|
that is why CI uses a `windows-2022` and an `ubuntu-22.04` runner.
|
||||||
|
|
||||||
|
The output directory is the whole distributable: the `Starter` executable, the
|
||||||
|
Skia/HarfBuzz native libraries and `startercfg/` with the translations and the
|
||||||
|
default key bindings. The `.dbg` (or `.pdb`) file next to it is debug symbols and
|
||||||
|
can be dropped from a release. Trim/AOT analysis warnings during the publish are
|
||||||
|
expected and do not fail the build.
|
||||||
|
|
||||||
|
CI builds both runtimes on every push and pull request to `main` and `staging`,
|
||||||
|
and on manual dispatch. Pushes additionally replace a rolling pre-release with
|
||||||
|
the packaged artifacts — tagged `latest` for `main` and `staging` for `staging`.
|
||||||
|
|
||||||
|
## Development build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet build StarterNG/StarterNG.csproj
|
||||||
|
```
|
||||||
@@ -5,18 +5,12 @@
|
|||||||
x:Class="StarterNG.App"
|
x:Class="StarterNG.App"
|
||||||
x:CompileBindings="True"
|
x:CompileBindings="True"
|
||||||
RequestedThemeVariant="Dark">
|
RequestedThemeVariant="Dark">
|
||||||
<!-- Dark variant matches the original Starter's "Carbon" VCL style. -->
|
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
<materialIcons:MaterialIconStyles />
|
<materialIcons:MaterialIconStyles />
|
||||||
<StyleInclude Source="avares://Starter/Styles/Carbon.axaml" />
|
<StyleInclude Source="avares://Starter/Styles/Carbon.axaml" />
|
||||||
|
|
||||||
<!-- Extending the client area (MainWindow) makes Avalonia 12 draw an extra
|
|
||||||
full-screen toggle in the window chrome. The chrome lives in its own host
|
|
||||||
(not the window's visual tree), so it is hidden here, globally, by the
|
|
||||||
decoration role Avalonia tags it with. Standard minimise / maximise /
|
|
||||||
close are left untouched. -->
|
|
||||||
<Style Selector="Button[(chrome|WindowDecorationProperties.ElementRole)=FullScreenButton]">
|
<Style Selector="Button[(chrome|WindowDecorationProperties.ElementRole)=FullScreenButton]">
|
||||||
<Setter Property="IsVisible" Value="False" />
|
<Setter Property="IsVisible" Value="False" />
|
||||||
</Style>
|
</Style>
|
||||||
@@ -24,9 +18,6 @@
|
|||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<PathGeometry x:Key="DiscordLogo" Figures="M 81.15 0 c -1.2376 2.1973 -2.3489 4.4704 -3.3591 6.794 c -9.5975 -1.4396 -19.3718 -1.4396 -28.9945 0 c -0.985 -2.3236 -2.1216 -4.5967 -3.3591 -6.794 c -9.0166 1.5407 -17.8059 4.2431 -26.1405 8.0568 C 2.779 32.5304 -1.6914 56.3725 0.5312 79.8863 c 9.6732 7.1476 20.5083 12.603 32.0505 16.0884 c 2.6014 -3.4854 4.8998 -7.1981 6.8698 -11.0623 c -3.738 -1.3891 -7.3497 -3.1318 -10.8098 -5.1523 c 0.9092 -0.6567 1.7932 -1.3386 2.6519 -1.9953 c 20.281 9.547 43.7696 9.547 64.0758 0 c 0.8587 0.7072 1.7427 1.3891 2.6519 1.9953 c -3.4601 2.0457 -7.0718 3.7632 -10.835 5.1776 c 1.97 3.8642 4.2683 7.5769 6.8698 11.0623 c 11.5419 -3.4854 22.3769 -8.9156 32.0509 -16.0631 c 2.626 -27.2771 -4.496 -50.9172 -18.817 -71.8548 C 98.9811 4.2684 90.1918 1.5659 81.1752 0.0505 l -0.0252 -0.0505 Z M 42.2802 65.4144 c -6.2383 0 -11.4159 -5.6575 -11.4159 -12.6535 s 4.9755 -12.6788 11.3907 -12.6788 s 11.5169 5.708 11.4159 12.6788 c -0.101 6.9708 -5.026 12.6535 -11.3907 12.6535 Z M 84.3576 65.4144 c -6.2637 0 -11.3907 -5.6575 -11.3907 -12.6535 s 4.9755 -12.6788 11.3907 -12.6788 s 11.4917 5.708 11.3906 12.6788 c -0.101 6.9708 -5.026 12.6535 -11.3906 12.6535 Z" FillRule="NonZero"/>
|
<PathGeometry x:Key="DiscordLogo" Figures="M 81.15 0 c -1.2376 2.1973 -2.3489 4.4704 -3.3591 6.794 c -9.5975 -1.4396 -19.3718 -1.4396 -28.9945 0 c -0.985 -2.3236 -2.1216 -4.5967 -3.3591 -6.794 c -9.0166 1.5407 -17.8059 4.2431 -26.1405 8.0568 C 2.779 32.5304 -1.6914 56.3725 0.5312 79.8863 c 9.6732 7.1476 20.5083 12.603 32.0505 16.0884 c 2.6014 -3.4854 4.8998 -7.1981 6.8698 -11.0623 c -3.738 -1.3891 -7.3497 -3.1318 -10.8098 -5.1523 c 0.9092 -0.6567 1.7932 -1.3386 2.6519 -1.9953 c 20.281 9.547 43.7696 9.547 64.0758 0 c 0.8587 0.7072 1.7427 1.3891 2.6519 1.9953 c -3.4601 2.0457 -7.0718 3.7632 -10.835 5.1776 c 1.97 3.8642 4.2683 7.5769 6.8698 11.0623 c 11.5419 -3.4854 22.3769 -8.9156 32.0509 -16.0631 c 2.626 -27.2771 -4.496 -50.9172 -18.817 -71.8548 C 98.9811 4.2684 90.1918 1.5659 81.1752 0.0505 l -0.0252 -0.0505 Z M 42.2802 65.4144 c -6.2383 0 -11.4159 -5.6575 -11.4159 -12.6535 s 4.9755 -12.6788 11.3907 -12.6788 s 11.5169 5.708 11.4159 12.6788 c -0.101 6.9708 -5.026 12.6535 -11.3907 12.6535 Z M 84.3576 65.4144 c -6.2637 0 -11.3907 -5.6575 -11.3907 -12.6535 s 4.9755 -12.6788 11.3907 -12.6788 s 11.4917 5.708 11.3906 12.6788 c -0.101 6.9708 -5.026 12.6535 -11.3906 12.6535 Z" FillRule="NonZero"/>
|
||||||
|
|
||||||
<!-- Override the system accent (the OS accent can be red) so every
|
|
||||||
accent-driven element - list/tree selection, checkboxes,
|
|
||||||
sliders - uses the MaSzyna green instead. -->
|
|
||||||
<Color x:Key="SystemAccentColor">#177f00</Color>
|
<Color x:Key="SystemAccentColor">#177f00</Color>
|
||||||
<Color x:Key="SystemAccentColorDark1">#136B00</Color>
|
<Color x:Key="SystemAccentColorDark1">#136B00</Color>
|
||||||
<Color x:Key="SystemAccentColorDark2">#0F5600</Color>
|
<Color x:Key="SystemAccentColorDark2">#0F5600</Color>
|
||||||
@@ -39,5 +30,13 @@
|
|||||||
<SolidColorBrush x:Key="AccentFillColorTertiaryBrush" Color="#136B00" />
|
<SolidColorBrush x:Key="AccentFillColorTertiaryBrush" Color="#136B00" />
|
||||||
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="#177f00" />
|
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="#177f00" />
|
||||||
<SolidColorBrush x:Key="SystemControlHighlightListAccentMediumBrush" Color="#1E9000" />
|
<SolidColorBrush x:Key="SystemControlHighlightListAccentMediumBrush" Color="#1E9000" />
|
||||||
|
|
||||||
|
<GridLength x:Key="SliderPreContentMargin">6</GridLength>
|
||||||
|
<GridLength x:Key="SliderPostContentMargin">6</GridLength>
|
||||||
|
<x:Double x:Key="SliderHorizontalHeight">24</x:Double>
|
||||||
|
<x:Double x:Key="SliderHorizontalThumbWidth">12</x:Double>
|
||||||
|
<x:Double x:Key="SliderHorizontalThumbHeight">12</x:Double>
|
||||||
|
<CornerRadius x:Key="SliderThumbCornerRadius">2</CornerRadius>
|
||||||
|
<x:Double x:Key="SliderTrackThemeHeight">4</x:Double>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
|||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using StarterNG.Classes;
|
using StarterNG.Classes;
|
||||||
|
using StarterNG.Infrastructure;
|
||||||
using StarterNG.Services;
|
using StarterNG.Services;
|
||||||
|
|
||||||
namespace StarterNG;
|
namespace StarterNG;
|
||||||
@@ -45,6 +46,30 @@ public partial class App : Application
|
|||||||
|
|
||||||
var resolvedName = Loc.Load(langNameOrCode);
|
var resolvedName = Loc.Load(langNameOrCode);
|
||||||
Settings.Instance.Language = resolvedName;
|
Settings.Instance.Language = resolvedName;
|
||||||
|
ApplyCulture(Loc.CurrentLangCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyCulture(string langCode)
|
||||||
|
{
|
||||||
|
CultureInfo culture;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
culture = langCode switch
|
||||||
|
{
|
||||||
|
"" or "en" => CultureInfo.InvariantCulture,
|
||||||
|
"cz" => CultureInfo.GetCultureInfo("cs"),
|
||||||
|
_ => CultureInfo.GetCultureInfo(langCode)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (CultureNotFoundException)
|
||||||
|
{
|
||||||
|
culture = CultureInfo.InvariantCulture;
|
||||||
|
}
|
||||||
|
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
||||||
|
CultureInfo.CurrentCulture = culture;
|
||||||
|
CultureInfo.CurrentUICulture = culture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
@@ -56,13 +81,23 @@ public partial class App : Application
|
|||||||
|
|
||||||
desktop.ShutdownRequested += (_, _) =>
|
desktop.ShutdownRequested += (_, _) =>
|
||||||
{
|
{
|
||||||
Settings.Instance.CaptureAndSave();
|
try { Settings.Instance.CaptureAndSave(); }
|
||||||
|
catch (Exception ex) { Diagnostics.Log("Settings save", ex); }
|
||||||
|
|
||||||
Settings.Instance.DumpMissingVehicleLog();
|
Settings.Instance.DumpMissingVehicleLog();
|
||||||
if (KeyboardConfig.Instance.Dirty)
|
|
||||||
KeyboardConfig.Instance.Save();
|
try
|
||||||
|
{
|
||||||
|
if (KeyboardConfig.Instance.Dirty)
|
||||||
|
KeyboardConfig.Instance.Save();
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Diagnostics.Log("Keyboard config save", ex); }
|
||||||
|
|
||||||
|
Diagnostics.Flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
SplashWindow? splash = null;
|
SplashWindow? splash = null;
|
||||||
|
bool mainReady = false;
|
||||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@@ -71,6 +106,8 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
|
if (mainReady)
|
||||||
|
return;
|
||||||
|
|
||||||
if (sw.ElapsedMilliseconds > 300 && splash == null)
|
if (sw.ElapsedMilliseconds > 300 && splash == null)
|
||||||
{
|
{
|
||||||
@@ -83,14 +120,47 @@ public partial class App : Application
|
|||||||
});
|
});
|
||||||
|
|
||||||
GameData.Instance.Load(progress);
|
GameData.Instance.Load(progress);
|
||||||
}).ContinueWith(_ =>
|
}).ContinueWith(load =>
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
var main = new MainWindow();
|
MainWindow main;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
main = new MainWindow();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log("Main window", ex);
|
||||||
|
Diagnostics.Flush();
|
||||||
|
Console.Error.WriteLine(ex);
|
||||||
|
mainReady = true;
|
||||||
|
splash?.Close();
|
||||||
|
desktop.Shutdown(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainReady = true;
|
||||||
desktop.MainWindow = main;
|
desktop.MainWindow = main;
|
||||||
main.Show();
|
main.Show();
|
||||||
splash?.Close();
|
splash?.Close();
|
||||||
|
splash = null;
|
||||||
|
|
||||||
|
if (load.IsFaulted && load.Exception is { } error)
|
||||||
|
{
|
||||||
|
var inner = error.GetBaseException();
|
||||||
|
Diagnostics.Log("Game data load", inner);
|
||||||
|
await Diagnostics.ReportAsync(
|
||||||
|
$"{Loc["FaultLoadData"]}{Environment.NewLine}{Environment.NewLine}{Loc["FaultDetail"]} {inner.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var faults = Diagnostics.CheckInstallation();
|
||||||
|
if (faults.Count > 0)
|
||||||
|
{
|
||||||
|
await Diagnostics.ReportAsync(
|
||||||
|
string.Join(Environment.NewLine, faults) + Environment.NewLine + Environment.NewLine +
|
||||||
|
Loc["FaultBadInstall"]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -46,6 +46,17 @@ public sealed class GameData
|
|||||||
catch { }
|
catch { }
|
||||||
done++;
|
done++;
|
||||||
}
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int legacy = Vehicles.ImportLegacy();
|
||||||
|
if (legacy > 0)
|
||||||
|
Infrastructure.Diagnostics.Log($"textures.txt: wczytano {legacy} tekstur");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Infrastructure.Diagnostics.Log("textures.txt import", ex);
|
||||||
|
}
|
||||||
|
|
||||||
Vehicles.EndLoad();
|
Vehicles.EndLoad();
|
||||||
|
|
||||||
VehicleDatabase.PreloadMiniIndex(miniDir);
|
VehicleDatabase.PreloadMiniIndex(miniDir);
|
||||||
|
|||||||
208
StarterNG/Classes/LegacyTextureDatabase.cs
Normal file
208
StarterNG/Classes/LegacyTextureDatabase.cs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
|
public static class LegacyTextureDatabase
|
||||||
|
{
|
||||||
|
public static List<string> EnumerateFiles(string dynamicRoot = "dynamic")
|
||||||
|
{
|
||||||
|
var files = new List<string>();
|
||||||
|
if (!Directory.Exists(dynamicRoot))
|
||||||
|
return files;
|
||||||
|
|
||||||
|
foreach (string group in Directory.EnumerateDirectories(dynamicRoot))
|
||||||
|
foreach (string sub in Directory.EnumerateDirectories(group))
|
||||||
|
{
|
||||||
|
string path = Path.Combine(sub, "textures.txt");
|
||||||
|
if (File.Exists(path))
|
||||||
|
files.Add(path);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VehicleEntry? Parse(string path, string dynamicRoot = "dynamic")
|
||||||
|
{
|
||||||
|
string[] lines;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lines = File.ReadAllLines(path, Encoding.GetEncoding(1250));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Infrastructure.Diagnostics.Log($"textures.txt {path}", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string dir = RelativeDirectory(path, dynamicRoot);
|
||||||
|
var entry = new VehicleEntry { Uuid = "legacy:" + dir };
|
||||||
|
|
||||||
|
bool archived = false;
|
||||||
|
string categorySign = "*";
|
||||||
|
int setSize = 0;
|
||||||
|
var pending = new List<string>();
|
||||||
|
|
||||||
|
foreach (string raw in lines)
|
||||||
|
{
|
||||||
|
string line = raw.Trim();
|
||||||
|
if (line.Length == 0) continue;
|
||||||
|
|
||||||
|
char c = line[0];
|
||||||
|
if (c is '#' or '@' or '*') continue;
|
||||||
|
|
||||||
|
if (line.StartsWith("$a", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
archived = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '^')
|
||||||
|
{
|
||||||
|
FlushSet(entry, pending, setSize);
|
||||||
|
setSize = line.Length > 1 && char.IsDigit(line[1]) ? line[1] - '0' : 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.StartsWith("!=", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
categorySign = line.Length > 2 ? line[2].ToString() : "*";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.IndexOf('=') < 0) continue;
|
||||||
|
if (line.StartsWith("//", StringComparison.Ordinal)) continue;
|
||||||
|
|
||||||
|
var texture = ParseEntryLine(line, dir, categorySign, archived, entry);
|
||||||
|
if (texture is null) continue;
|
||||||
|
|
||||||
|
entry.Textures.Add(texture);
|
||||||
|
|
||||||
|
if (setSize > 1)
|
||||||
|
{
|
||||||
|
pending.Add(texture.Uuid!);
|
||||||
|
if (pending.Count == setSize)
|
||||||
|
FlushSet(entry, pending, setSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushSet(entry, pending, setSize);
|
||||||
|
return entry.Textures.Count > 0 ? entry : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FlushSet(VehicleEntry entry, List<string> pending, int size)
|
||||||
|
{
|
||||||
|
if (pending.Count > 1)
|
||||||
|
entry.Sets.Add(new VehicleSet
|
||||||
|
{
|
||||||
|
Uuid = "legacy-set:" + pending[0],
|
||||||
|
Mode = "sequence",
|
||||||
|
Count = size > 0 ? size : pending.Count,
|
||||||
|
TextureRefs = new List<string>(pending)
|
||||||
|
});
|
||||||
|
pending.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VehicleTexture? ParseEntryLine(string line, string dir,
|
||||||
|
string categorySign, bool archived, VehicleEntry entry)
|
||||||
|
{
|
||||||
|
string desc = "";
|
||||||
|
int comment = line.IndexOf("//", StringComparison.Ordinal);
|
||||||
|
if (comment >= 0)
|
||||||
|
{
|
||||||
|
desc = line[(comment + 2)..].Trim();
|
||||||
|
line = line[..comment].Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = line.Split('=');
|
||||||
|
if (parts.Length < 2) return null;
|
||||||
|
|
||||||
|
string skin = StripExtension(parts[0].Trim());
|
||||||
|
if (skin.Length == 0) return null;
|
||||||
|
|
||||||
|
var specs = new List<(string Model, string Mini, string MiniD)>();
|
||||||
|
for (int i = 1; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
var f = parts[i].Split(',');
|
||||||
|
if (f.Length < 2) continue;
|
||||||
|
specs.Add((f[0].Trim(), f[1].Trim(), f.Length > 2 ? f[2].Trim() : ""));
|
||||||
|
}
|
||||||
|
if (specs.Count == 0) return null;
|
||||||
|
|
||||||
|
var first = specs[0];
|
||||||
|
string groupId = $"legacy:{dir}:{first.Mini}:{categorySign}:{(archived ? 1 : 0)}";
|
||||||
|
|
||||||
|
if (!entry.Groups.Exists(g => g.Id == groupId))
|
||||||
|
entry.Groups.Add(new VehicleGroup
|
||||||
|
{
|
||||||
|
Id = groupId,
|
||||||
|
Category = categorySign,
|
||||||
|
Mini = first.Mini,
|
||||||
|
Archived = archived,
|
||||||
|
Implicit = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var texture = new VehicleTexture
|
||||||
|
{
|
||||||
|
Uuid = $"legacy:{dir}{skin}",
|
||||||
|
Directory = dir,
|
||||||
|
Skinfile = skin,
|
||||||
|
Model = first.Model,
|
||||||
|
Group = groupId,
|
||||||
|
MiniRef = first.Mini,
|
||||||
|
TextureMini = string.IsNullOrEmpty(first.MiniD) ? null : first.MiniD,
|
||||||
|
Meta = ParseDescription(desc)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 1; i < specs.Count; i++)
|
||||||
|
texture.Aliases.Add(new VehicleAlias
|
||||||
|
{
|
||||||
|
Model = specs[i].Model,
|
||||||
|
MiniRef = specs[i].Mini,
|
||||||
|
TextureMini = string.IsNullOrEmpty(specs[i].MiniD) ? null : specs[i].MiniD
|
||||||
|
});
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VehicleMeta? ParseDescription(string desc)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(desc)) return null;
|
||||||
|
|
||||||
|
var f = desc.Replace('_', ' ').Split(',');
|
||||||
|
string At(int i) => i < f.Length ? f[i].Trim() : "";
|
||||||
|
|
||||||
|
return new VehicleMeta
|
||||||
|
{
|
||||||
|
Raw = desc,
|
||||||
|
Version = At(0),
|
||||||
|
Vehicle = At(1),
|
||||||
|
Operator = At(2),
|
||||||
|
Depot = At(3),
|
||||||
|
RevisionDate = At(4),
|
||||||
|
RevisionPlace = At(5),
|
||||||
|
TextureAuthor = At(6),
|
||||||
|
PhotoAuthor = At(7)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string StripExtension(string name)
|
||||||
|
{
|
||||||
|
int dot = name.LastIndexOf('.');
|
||||||
|
return dot > 0 ? name[..dot] : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RelativeDirectory(string texturesPath, string dynamicRoot)
|
||||||
|
{
|
||||||
|
string dir = Path.GetDirectoryName(texturesPath) ?? "";
|
||||||
|
string full = Path.GetFullPath(dir);
|
||||||
|
string root = Path.GetFullPath(dynamicRoot);
|
||||||
|
|
||||||
|
string rel = full.StartsWith(root, StringComparison.OrdinalIgnoreCase)
|
||||||
|
? full[root.Length..].TrimStart(Path.DirectorySeparatorChar, '/')
|
||||||
|
: dir;
|
||||||
|
|
||||||
|
return rel.Replace('\\', '/').TrimEnd('/') + "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,8 @@ public sealed class Physics
|
|||||||
|
|
||||||
public static void PreloadIndex(string dynamicRoot = "dynamic") => FizIndex(dynamicRoot);
|
public static void PreloadIndex(string dynamicRoot = "dynamic") => FizIndex(dynamicRoot);
|
||||||
|
|
||||||
|
public static int IndexedCount => _fizIndex?.Count ?? 0;
|
||||||
|
|
||||||
private static Dictionary<string, string> FizIndex(string dynamicRoot)
|
private static Dictionary<string, string> FizIndex(string dynamicRoot)
|
||||||
{
|
{
|
||||||
if (_fizIndex != null)
|
if (_fizIndex != null)
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ public static class PresetStore
|
|||||||
added++;
|
added++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Infrastructure.Diagnostics.Log($"Import {path}", ex);
|
||||||
|
}
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,11 @@ public class Scenery
|
|||||||
public double Temperature = 15;
|
public double Temperature = 15;
|
||||||
public int FogStart = 10;
|
public int FogStart = 10;
|
||||||
public int FogEnd = 2000;
|
public int FogEnd = 2000;
|
||||||
|
|
||||||
|
public const int FogMin = 10;
|
||||||
|
public const int FogMax = 10000;
|
||||||
public double Overcast = 0;
|
public double Overcast = 0;
|
||||||
|
public bool OvercastRandom;
|
||||||
|
|
||||||
public bool HasWeather;
|
public bool HasWeather;
|
||||||
|
|
||||||
@@ -61,6 +65,7 @@ public class Scenery
|
|||||||
private double _origTemperature = 15;
|
private double _origTemperature = 15;
|
||||||
private int _origFogEnd = 2000;
|
private int _origFogEnd = 2000;
|
||||||
private double _origOvercast;
|
private double _origOvercast;
|
||||||
|
private bool _origOvercastRandom;
|
||||||
|
|
||||||
private readonly string _template;
|
private readonly string _template;
|
||||||
|
|
||||||
@@ -68,8 +73,6 @@ public class Scenery
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(Name))
|
|
||||||
return SceneryI18n.T(Name);
|
|
||||||
return System.IO.Path.GetFileNameWithoutExtension(Path);
|
return System.IO.Path.GetFileNameWithoutExtension(Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +152,7 @@ public class Scenery
|
|||||||
RangeMin = float.Parse(m.Groups[2].Value, inv),
|
RangeMin = float.Parse(m.Groups[2].Value, inv),
|
||||||
Name = name,
|
Name = name,
|
||||||
DataFolder = m.Groups[4].Value,
|
DataFolder = m.Groups[4].Value,
|
||||||
SkinFile = m.Groups[5].Value,
|
SkinFile = Dynamic.StripSkinExtension(m.Groups[5].Value),
|
||||||
MmdFile = m.Groups[6].Value,
|
MmdFile = m.Groups[6].Value,
|
||||||
PathName = m.Groups[7].Value,
|
PathName = m.Groups[7].Value,
|
||||||
Offset = float.Parse(m.Groups[8].Value, inv),
|
Offset = float.Parse(m.Groups[8].Value, inv),
|
||||||
@@ -356,8 +359,8 @@ public class Scenery
|
|||||||
int.TryParse(nums[3], out int fogStart) &&
|
int.TryParse(nums[3], out int fogStart) &&
|
||||||
int.TryParse(nums[4], out int fogEnd))
|
int.TryParse(nums[4], out int fogEnd))
|
||||||
{
|
{
|
||||||
FogStart = Math.Clamp(fogStart, 10, 2500);
|
FogStart = Math.Clamp(fogStart, FogMin, FogMax);
|
||||||
fogEnd = Math.Clamp(fogEnd, FogStart, 2500);
|
fogEnd = Math.Clamp(fogEnd, FogStart, FogMax);
|
||||||
FogEnd = fogEnd > FogStart
|
FogEnd = fogEnd > FogStart
|
||||||
? Random.Shared.Next(FogStart, fogEnd + 1)
|
? Random.Shared.Next(FogStart, fogEnd + 1)
|
||||||
: fogEnd;
|
: fogEnd;
|
||||||
@@ -378,8 +381,17 @@ public class Scenery
|
|||||||
_origTemperature = Temperature;
|
_origTemperature = Temperature;
|
||||||
_origFogEnd = FogEnd;
|
_origFogEnd = FogEnd;
|
||||||
_origOvercast = Overcast;
|
_origOvercast = Overcast;
|
||||||
|
_origOvercastRandom = OvercastRandom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool WeatherChanged =>
|
||||||
|
Day != _origDay ||
|
||||||
|
ScenarioTimeOverride != _origOverride ||
|
||||||
|
Math.Abs(Temperature - _origTemperature) > 0.001 ||
|
||||||
|
FogEnd != _origFogEnd ||
|
||||||
|
OvercastRandom != _origOvercastRandom ||
|
||||||
|
(!OvercastRandom && Math.Abs(Overcast - _origOvercast) > 0.001);
|
||||||
|
|
||||||
public void RestoreWeather()
|
public void RestoreWeather()
|
||||||
{
|
{
|
||||||
Time = _origTime;
|
Time = _origTime;
|
||||||
@@ -388,15 +400,24 @@ public class Scenery
|
|||||||
Temperature = _origTemperature;
|
Temperature = _origTemperature;
|
||||||
FogEnd = _origFogEnd;
|
FogEnd = _origFogEnd;
|
||||||
Overcast = _origOvercast;
|
Overcast = _origOvercast;
|
||||||
|
OvercastRandom = _origOvercastRandom;
|
||||||
WeatherDirty = true;
|
WeatherDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly double[] OvercastPresets = { -1.5, 0, 0.3, 0.7, 1, 1.1, 1.5 };
|
||||||
|
|
||||||
|
private double EffectiveOvercast() =>
|
||||||
|
OvercastRandom ? OvercastPresets[Random.Shared.Next(OvercastPresets.Length)] : Overcast;
|
||||||
|
|
||||||
private string RewriteWeather(string text)
|
private string RewriteWeather(string text)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
string s = text;
|
string s = text;
|
||||||
|
|
||||||
|
string carried = CarriedConfigLines(s);
|
||||||
|
|
||||||
s = Regex.Replace(s, @"(?is)\bconfig\b.*?\bendconfig\b", " ");
|
s = Regex.Replace(s, @"(?is)\bconfig\b.*?\bendconfig\b", " ");
|
||||||
s = Regex.Replace(s, @"(?is)\btime\b\s+\d[^\r\n]*?\bendtime\b", " ");
|
s = Regex.Replace(s, @"(?is)\btime\b\s+\d[^\r\n]*?\bendtime\b", " ");
|
||||||
s = Regex.Replace(s, @"(?is)\batmo\b.*?\bendatmo\b", " ");
|
s = Regex.Replace(s, @"(?is)\batmo\b.*?\bendatmo\b", " ");
|
||||||
@@ -410,10 +431,11 @@ public class Scenery
|
|||||||
"config\r\n" +
|
"config\r\n" +
|
||||||
$"movelight {Day}\r\n" +
|
$"movelight {Day}\r\n" +
|
||||||
$"scenario.weather.temperature {Temperature.ToString(inv)}\r\n" +
|
$"scenario.weather.temperature {Temperature.ToString(inv)}\r\n" +
|
||||||
$"scenario.time.override {ScenarioTimeOverride}\r\n" +
|
$"scenario.time.override {DelphiHourMinute(ScenarioTimeOverride)}\r\n" +
|
||||||
|
carried +
|
||||||
"endconfig\r\n" +
|
"endconfig\r\n" +
|
||||||
$"atmo 0 0 0 {FogEnd} {FogEnd} 0 0 0 {Overcast.ToString(inv)} endatmo\r\n" +
|
$"atmo 0 0 0 {FogEnd} {FogEnd} 0 0 0 {EffectiveOvercast().ToString(inv)} endatmo\r\n" +
|
||||||
$"time {Time} 0 0 endtime\r\n";
|
$"time {DelphiHourMinute(Time)} 0 0 endtime\r\n";
|
||||||
|
|
||||||
return config + s;
|
return config + s;
|
||||||
}
|
}
|
||||||
@@ -423,6 +445,36 @@ public class Scenery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string DelphiHourMinute(string hhmm)
|
||||||
|
{
|
||||||
|
int colon = hhmm.IndexOf(':');
|
||||||
|
if (colon <= 0) return hhmm;
|
||||||
|
return hhmm[..colon].TrimStart('0') is { Length: > 0 } h
|
||||||
|
? h + hhmm[colon..]
|
||||||
|
: "0" + hhmm[colon..];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Regex ManagedConfigLine = new(
|
||||||
|
@"^\s*(movelight|scenario\.weather\.temperature|scenario\.time\.override)\b",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static string CarriedConfigLines(string text)
|
||||||
|
{
|
||||||
|
var block = Regex.Match(text, @"(?is)\bconfig\b(.*?)\bendconfig\b");
|
||||||
|
if (!block.Success)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (string raw in block.Groups[1].Value.Split('\n'))
|
||||||
|
{
|
||||||
|
string line = raw.TrimEnd('\r').Trim();
|
||||||
|
if (line.Length == 0 || ManagedConfigLine.IsMatch(line))
|
||||||
|
continue;
|
||||||
|
sb.Append(line).Append("\r\n");
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private static string MatchAllDirectives(string content, string letter)
|
private static string MatchAllDirectives(string content, string letter)
|
||||||
{
|
{
|
||||||
var matches = Regex.Matches(
|
var matches = Regex.Matches(
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ public sealed class Settings
|
|||||||
public int VehiclesVolume = 100;
|
public int VehiclesVolume = 100;
|
||||||
public int PositionalVolume = 100;
|
public int PositionalVolume = 100;
|
||||||
public int AmbientVolume = 100;
|
public int AmbientVolume = 100;
|
||||||
|
public int VolumePaused = 100;
|
||||||
|
public bool SkipPipeline;
|
||||||
|
public string PyScreenPriority = "normal";
|
||||||
|
public bool DebugLogVisible;
|
||||||
|
private int _debugLogExtraBits;
|
||||||
|
|
||||||
public bool CompressTextures = true;
|
public bool CompressTextures = true;
|
||||||
public bool ScaleSpeculars = true;
|
public bool ScaleSpeculars = true;
|
||||||
@@ -130,6 +135,10 @@ public sealed class Settings
|
|||||||
private string _savePath = string.Empty;
|
private string _savePath = string.Empty;
|
||||||
private DateTime _configAgeUtc = DateTime.MinValue;
|
private DateTime _configAgeUtc = DateTime.MinValue;
|
||||||
|
|
||||||
|
public string LoadedFrom { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public string SavePath => _savePath;
|
||||||
|
|
||||||
public static string UserConfigPath()
|
public static string UserConfigPath()
|
||||||
{
|
{
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
@@ -289,6 +298,7 @@ public sealed class Settings
|
|||||||
{
|
{
|
||||||
_savePath = UserConfigPath();
|
_savePath = UserConfigPath();
|
||||||
string source = File.Exists(_savePath) ? _savePath : DefaultConfigPath();
|
string source = File.Exists(_savePath) ? _savePath : DefaultConfigPath();
|
||||||
|
LoadedFrom = source;
|
||||||
LoadFrom(source);
|
LoadFrom(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,11 +404,12 @@ public sealed class Settings
|
|||||||
{
|
{
|
||||||
var lines = GameData.Instance.Vehicles.CollectMissingAssetLines();
|
var lines = GameData.Instance.Vehicles.CollectMissingAssetLines();
|
||||||
if (lines.Count == 0) return;
|
if (lines.Count == 0) return;
|
||||||
string dir = Path.Combine(Directory.GetCurrentDirectory(), "starter");
|
Infrastructure.Diagnostics.Log(lines);
|
||||||
Directory.CreateDirectory(dir);
|
}
|
||||||
File.WriteAllLines(Path.Combine(dir, "bledy.txt"), lines);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Infrastructure.Diagnostics.Log("Missing vehicle log", ex);
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadFromConfig()
|
private void ReadFromConfig()
|
||||||
@@ -492,7 +503,10 @@ public sealed class Settings
|
|||||||
EnableTraction = c.GetBool("enabletraction", true);
|
EnableTraction = c.GetBool("enabletraction", true);
|
||||||
LiveTraction = c.GetBool("livetraction", true);
|
LiveTraction = c.GetBool("livetraction", true);
|
||||||
PhysicsLog = c.GetBool("physicslog", false);
|
PhysicsLog = c.GetBool("physicslog", false);
|
||||||
DebugLog = c.GetInt("debuglog", 3) != 0;
|
int debugLog = c.GetInt("debuglog", 3);
|
||||||
|
DebugLog = (debugLog & 1) != 0;
|
||||||
|
DebugLogVisible = (debugLog & 2) != 0;
|
||||||
|
_debugLogExtraBits = debugLog & ~3;
|
||||||
MultipleLogs = c.GetBool("multiplelogs", false);
|
MultipleLogs = c.GetBool("multiplelogs", false);
|
||||||
DisplaySimulation = !c.GetBool("gfx.skiprendering", false);
|
DisplaySimulation = !c.GetBool("gfx.skiprendering", false);
|
||||||
CrashDamage = c.GetBool("crashdamage", true);
|
CrashDamage = c.GetBool("crashdamage", true);
|
||||||
@@ -506,6 +520,9 @@ public sealed class Settings
|
|||||||
VehiclesVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.vehicle", 1.0) * 100), 1, 100);
|
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);
|
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);
|
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");
|
||||||
|
|
||||||
CompressTextures = c.GetBool("compresstex", true);
|
CompressTextures = c.GetBool("compresstex", true);
|
||||||
ScaleSpeculars = c.GetBool("scalespeculars", true);
|
ScaleSpeculars = c.GetBool("scalespeculars", true);
|
||||||
@@ -613,7 +630,8 @@ public sealed class Settings
|
|||||||
c.SetBool("enabletraction", EnableTraction);
|
c.SetBool("enabletraction", EnableTraction);
|
||||||
c.SetBool("livetraction", LiveTraction);
|
c.SetBool("livetraction", LiveTraction);
|
||||||
c.SetBool("physicslog", PhysicsLog);
|
c.SetBool("physicslog", PhysicsLog);
|
||||||
c.SetInt("debuglog", DebugLog ? 3 : 0);
|
c.SetInt("debuglog",
|
||||||
|
(DebugLog ? 1 : 0) | (DebugLogVisible ? 2 : 0) | _debugLogExtraBits);
|
||||||
c.SetBool("multiplelogs", MultipleLogs);
|
c.SetBool("multiplelogs", MultipleLogs);
|
||||||
c.SetBool("gfx.skiprendering", !DisplaySimulation);
|
c.SetBool("gfx.skiprendering", !DisplaySimulation);
|
||||||
c.SetBool("crashdamage", CrashDamage);
|
c.SetBool("crashdamage", CrashDamage);
|
||||||
@@ -627,6 +645,9 @@ public sealed class Settings
|
|||||||
c.SetDouble("sound.volume.vehicle", VehiclesVolume / 100.0);
|
c.SetDouble("sound.volume.vehicle", VehiclesVolume / 100.0);
|
||||||
c.SetDouble("sound.volume.positional", PositionalVolume / 100.0);
|
c.SetDouble("sound.volume.positional", PositionalVolume / 100.0);
|
||||||
c.SetDouble("sound.volume.ambient", AmbientVolume / 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.SetBool("compresstex", CompressTextures);
|
c.SetBool("compresstex", CompressTextures);
|
||||||
c.SetBool("scalespeculars", ScaleSpeculars);
|
c.SetBool("scalespeculars", ScaleSpeculars);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -107,7 +107,7 @@ public class Trainset
|
|||||||
nodeDynamic.Name = tokens[++i];
|
nodeDynamic.Name = tokens[++i];
|
||||||
i++;
|
i++;
|
||||||
nodeDynamic.DataFolder = tokens[++i];
|
nodeDynamic.DataFolder = tokens[++i];
|
||||||
nodeDynamic.SkinFile = tokens[++i];
|
nodeDynamic.SkinFile = Dynamic.StripSkinExtension(tokens[++i]);
|
||||||
nodeDynamic.MmdFile = tokens[++i];
|
nodeDynamic.MmdFile = tokens[++i];
|
||||||
nodeDynamic.Offset = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
|
nodeDynamic.Offset = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
@@ -158,30 +158,7 @@ public class Trainset
|
|||||||
|
|
||||||
string entry = sb.ToString();
|
string entry = sb.ToString();
|
||||||
foreach (Dynamic vehicle in Vehicles)
|
foreach (Dynamic vehicle in Vehicles)
|
||||||
{
|
entry += vehicle.ToTrainsetNode();
|
||||||
string driverType = "";
|
|
||||||
switch (vehicle.DriverType)
|
|
||||||
{
|
|
||||||
case eDriverType.Headdriver:
|
|
||||||
driverType = "headdriver";
|
|
||||||
break;
|
|
||||||
case eDriverType.Reardriver:
|
|
||||||
driverType = "reardriver";
|
|
||||||
break;
|
|
||||||
case eDriverType.Passenger:
|
|
||||||
driverType = "passenger";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
driverType = "nobody";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry +=
|
|
||||||
$"node {vehicle.RangeMax} {vehicle.RangeMin} {vehicle.Name} dynamic " +
|
|
||||||
$"{vehicle.DataFolder} {vehicle.SkinFile} {vehicle.MmdFile} " +
|
|
||||||
$"{vehicle.Offset.ToString(CultureInfo.InvariantCulture)} " +
|
|
||||||
$"{driverType} {vehicle.Coupling}{vehicle.WriteTrailing()} enddynamic\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -207,7 +184,11 @@ public class Dynamic
|
|||||||
set => Coupling.Flags = value;
|
set => Coupling.Flags = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? MaxLoad;
|
public const string PantState = "pantstate";
|
||||||
|
|
||||||
|
public const int PantStateMax = 3;
|
||||||
|
|
||||||
|
public int MaxLoad = -1;
|
||||||
|
|
||||||
public bool HasVelocity;
|
public bool HasVelocity;
|
||||||
|
|
||||||
@@ -217,25 +198,24 @@ public class Dynamic
|
|||||||
|
|
||||||
public string? LoadType;
|
public string? LoadType;
|
||||||
|
|
||||||
public List<string> Destinations = new();
|
|
||||||
|
|
||||||
public string? MiniName;
|
public string? MiniName;
|
||||||
|
|
||||||
public bool Flipped;
|
public bool Flipped;
|
||||||
|
|
||||||
|
public static bool IsPantStateType(string? type) =>
|
||||||
|
string.Equals(type, PantState, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public bool IsPantState => IsPantStateType(LoadType);
|
||||||
|
|
||||||
internal void ReadTrailing(List<string> t)
|
internal void ReadTrailing(List<string> t)
|
||||||
{
|
{
|
||||||
int p = 0;
|
int p = 0;
|
||||||
|
|
||||||
if (p < t.Count && (t[p][0] == 'L' || t[p][0] == 'l')
|
if (p < t.Count && t[p].Length > 0 && t[p][0] == 'L')
|
||||||
&& t[p].Length > 1 && char.IsDigit(t[p][1]))
|
|
||||||
MaxLoad = t[p++];
|
|
||||||
|
|
||||||
if (p < t.Count && float.TryParse(t[p], NumberStyles.Float,
|
|
||||||
CultureInfo.InvariantCulture, out float v))
|
|
||||||
{
|
{
|
||||||
Velocity = v;
|
if (int.TryParse(t[p].AsSpan(1), NumberStyles.Integer,
|
||||||
HasVelocity = true;
|
CultureInfo.InvariantCulture, out int ml))
|
||||||
|
MaxLoad = ml;
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,29 +224,30 @@ public class Dynamic
|
|||||||
{
|
{
|
||||||
LoadCount = lc;
|
LoadCount = lc;
|
||||||
p++;
|
p++;
|
||||||
if (lc > 0 && p < t.Count)
|
if (p < t.Count && (lc > 0 || IsPantStateType(t[p])))
|
||||||
LoadType = t[p++];
|
LoadType = t[p++];
|
||||||
}
|
}
|
||||||
|
|
||||||
while (p < t.Count)
|
}
|
||||||
Destinations.Add(t[p++]);
|
|
||||||
|
internal static string StripSkinExtension(string token)
|
||||||
|
{
|
||||||
|
int dot = token.LastIndexOf('.');
|
||||||
|
return dot > 0 ? token[..dot] : token;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string WriteTrailing()
|
internal string WriteTrailing()
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
if (MaxLoad != null)
|
if (MaxLoad >= 0)
|
||||||
sb.Append(' ').Append(MaxLoad);
|
sb.Append(" L").Append(MaxLoad.ToString(CultureInfo.InvariantCulture));
|
||||||
if (HasVelocity)
|
|
||||||
sb.Append(' ').Append(Velocity.ToString(CultureInfo.InvariantCulture));
|
bool loaded = !string.IsNullOrEmpty(LoadType);
|
||||||
if (LoadCount > 0)
|
sb.Append(' ').Append((loaded ? LoadCount : 0).ToString(CultureInfo.InvariantCulture));
|
||||||
{
|
|
||||||
sb.Append(' ').Append(LoadCount.ToString(CultureInfo.InvariantCulture));
|
if (loaded && (LoadCount > 0 || IsPantState))
|
||||||
if (!string.IsNullOrEmpty(LoadType))
|
sb.Append(' ').Append(LoadType);
|
||||||
sb.Append(' ').Append(LoadType);
|
|
||||||
}
|
|
||||||
foreach (string d in Destinations)
|
|
||||||
sb.Append(' ').Append(d);
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,11 +268,27 @@ public class Dynamic
|
|||||||
Velocity = Velocity,
|
Velocity = Velocity,
|
||||||
LoadCount = LoadCount,
|
LoadCount = LoadCount,
|
||||||
LoadType = LoadType,
|
LoadType = LoadType,
|
||||||
Destinations = new List<string>(Destinations),
|
|
||||||
MiniName = MiniName,
|
MiniName = MiniName,
|
||||||
Flipped = Flipped
|
Flipped = Flipped
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public string ToTrainsetNode()
|
||||||
|
{
|
||||||
|
string driver = DriverType switch
|
||||||
|
{
|
||||||
|
eDriverType.Headdriver => "headdriver",
|
||||||
|
eDriverType.Reardriver => "reardriver",
|
||||||
|
eDriverType.Passenger => "passenger",
|
||||||
|
_ => "nobody"
|
||||||
|
};
|
||||||
|
|
||||||
|
return
|
||||||
|
$"node {RangeMax} {RangeMin} {Name} dynamic " +
|
||||||
|
$"{DataFolder} {SkinFile} {MmdFile} " +
|
||||||
|
$"{Offset.ToString(CultureInfo.InvariantCulture)} " +
|
||||||
|
$"{driver} {Coupling}{WriteTrailing()} enddynamic\n";
|
||||||
|
}
|
||||||
|
|
||||||
public string ToLooseNode()
|
public string ToLooseNode()
|
||||||
{
|
{
|
||||||
string driver = DriverType switch
|
string driver = DriverType switch
|
||||||
@@ -374,7 +371,7 @@ public sealed class Coupling
|
|||||||
public void SetBrake(BrakeSetting? brake)
|
public void SetBrake(BrakeSetting? brake)
|
||||||
{
|
{
|
||||||
Parameters.RemoveAll(p => p.StartsWith("B", StringComparison.Ordinal));
|
Parameters.RemoveAll(p => p.StartsWith("B", StringComparison.Ordinal));
|
||||||
if (brake != null)
|
if (brake is { IsEmpty: false })
|
||||||
Parameters.Insert(0, brake.ToParameter());
|
Parameters.Insert(0, brake.ToParameter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,34 +414,39 @@ public sealed class Coupling
|
|||||||
|
|
||||||
public sealed class BrakeSetting
|
public sealed class BrakeSetting
|
||||||
{
|
{
|
||||||
public static readonly string[] Modes = { "R+Mg", "G", "P", "R", "Q", "O", "A" };
|
public static readonly string[] Modes = { "G", "P", "R", "M", "Q" };
|
||||||
|
|
||||||
public string Mode = "P";
|
public static readonly string[] Loads = { "T", "H", "F" };
|
||||||
|
|
||||||
|
public static readonly string[] Switches = { "0", "1" };
|
||||||
|
|
||||||
|
public string? Mode;
|
||||||
|
|
||||||
public string? Load;
|
public string? Load;
|
||||||
|
|
||||||
public string? Switch;
|
public string? Switch;
|
||||||
|
|
||||||
|
public bool IsEmpty => Mode is null;
|
||||||
|
|
||||||
public static BrakeSetting? FromParameter(string? param)
|
public static BrakeSetting? FromParameter(string? param)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(param) || param![0] != 'B' || param.Length < 2)
|
if (string.IsNullOrEmpty(param) || char.ToUpperInvariant(param![0]) != 'B')
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
string body = param.Substring(1);
|
string body = param.Substring(1).ToUpperInvariant();
|
||||||
string? mode = Modes.FirstOrDefault(m => body.StartsWith(m, StringComparison.Ordinal));
|
var b = new BrakeSetting
|
||||||
if (mode is null)
|
{
|
||||||
return null;
|
Mode = First(body, "M", "G", "P", "R", "Q"),
|
||||||
|
Load = First(body, Loads),
|
||||||
|
Switch = First(body, Switches)
|
||||||
|
};
|
||||||
|
return b.Mode is null && b.Load is null && b.Switch is null ? null : b;
|
||||||
|
|
||||||
var b = new BrakeSetting { Mode = mode };
|
static string? First(string body, params string[] codes) =>
|
||||||
int p = mode.Length;
|
Array.Find(codes, c => body.Contains(c, StringComparison.Ordinal));
|
||||||
if (p < body.Length && "THFA".IndexOf(body[p]) >= 0)
|
|
||||||
b.Load = body[p++].ToString();
|
|
||||||
if (p < body.Length && "01A".IndexOf(body[p]) >= 0)
|
|
||||||
b.Switch = body[p].ToString();
|
|
||||||
return b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToParameter() => "B" + Mode + (Load ?? "") + (Switch ?? "");
|
public string ToParameter() => "B" + Mode + Switch + Load;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class WheelSettings
|
public sealed class WheelSettings
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -173,6 +173,26 @@ public class VehicleDatabase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int ImportLegacy(string dynamicRoot = "dynamic")
|
||||||
|
{
|
||||||
|
int added = 0;
|
||||||
|
foreach (string file in LegacyTextureDatabase.EnumerateFiles(dynamicRoot))
|
||||||
|
{
|
||||||
|
var entry = LegacyTextureDatabase.Parse(file, dynamicRoot);
|
||||||
|
if (entry is null) continue;
|
||||||
|
|
||||||
|
entry.Textures.RemoveAll(t =>
|
||||||
|
TextureBySkin.ContainsKey(
|
||||||
|
Path.GetFileNameWithoutExtension(t.Skinfile).ToLowerInvariant()));
|
||||||
|
|
||||||
|
if (entry.Textures.Count == 0) continue;
|
||||||
|
|
||||||
|
added += entry.Textures.Count;
|
||||||
|
Ingest(entry);
|
||||||
|
}
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
private void Ingest(VehicleEntry entry)
|
private void Ingest(VehicleEntry entry)
|
||||||
{
|
{
|
||||||
foreach (var group in entry.Groups)
|
foreach (var group in entry.Groups)
|
||||||
@@ -265,7 +285,7 @@ public class VehicleDatabase
|
|||||||
|
|
||||||
public string? ResolveMiniName(VehicleTexture texture)
|
public string? ResolveMiniName(VehicleTexture texture)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(texture.TextureMini) && MiniPath(texture.TextureMini) != null)
|
if (HasMini(texture.TextureMini))
|
||||||
return texture.TextureMini;
|
return texture.TextureMini;
|
||||||
|
|
||||||
if (texture.Group != null && GroupsById.TryGetValue(texture.Group, out var grp)
|
if (texture.Group != null && GroupsById.TryGetValue(texture.Group, out var grp)
|
||||||
@@ -330,10 +350,26 @@ public class VehicleDatabase
|
|||||||
|
|
||||||
public static string? MiniPath(string? miniName, string miniDir = "textures/mini/")
|
public static string? MiniPath(string? miniName, string miniDir = "textures/mini/")
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(miniName)) return null;
|
|
||||||
|
|
||||||
var index = _miniIndex ??= BuildMiniIndex(miniDir);
|
var index = _miniIndex ??= BuildMiniIndex(miniDir);
|
||||||
return index.TryGetValue(miniName!.ToLowerInvariant(), out var path) ? path : null;
|
|
||||||
|
if (!string.IsNullOrEmpty(miniName) &&
|
||||||
|
index.TryGetValue(miniName!.ToLowerInvariant(), out var path))
|
||||||
|
return path;
|
||||||
|
|
||||||
|
return FallbackMiniPath(miniDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasMini(string? miniName, string miniDir = "textures/mini/")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(miniName)) return false;
|
||||||
|
var index = _miniIndex ??= BuildMiniIndex(miniDir);
|
||||||
|
return index.ContainsKey(miniName!.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? FallbackMiniPath(string miniDir = "textures/mini/")
|
||||||
|
{
|
||||||
|
var index = _miniIndex ??= BuildMiniIndex(miniDir);
|
||||||
|
return index.TryGetValue("other", out var other) ? other : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, string> BuildMiniIndex(string miniDir)
|
private static Dictionary<string, string> BuildMiniIndex(string miniDir)
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public static class TrainsetDisplay
|
|||||||
if (IsEmpty(trainset))
|
if (IsEmpty(trainset))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var db = GameData.Instance.Vehicles;
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
string? prevKey = null;
|
string? prevKey = null;
|
||||||
int run = 0;
|
int run = 0;
|
||||||
@@ -36,22 +35,16 @@ public static class TrainsetDisplay
|
|||||||
for (int i = 0; i < trainset.Vehicles.Count; i++)
|
for (int i = 0; i < trainset.Vehicles.Count; i++)
|
||||||
{
|
{
|
||||||
var v = trainset.Vehicles[i];
|
var v = trainset.Vehicles[i];
|
||||||
var tex = db.TextureForSkin(v.SkinFile);
|
|
||||||
string cat = tex?.ResolvedCategory ?? "";
|
|
||||||
bool powered = cat is "e" or "s" or "p" or "z" or "a";
|
|
||||||
|
|
||||||
if (i == 0 && powered)
|
if (i == 0 && IsPowered(v))
|
||||||
{
|
{
|
||||||
string head = !string.IsNullOrWhiteSpace(v.Name) ? v.Name! :
|
sb.Append(VehicleLabel(v, head: true));
|
||||||
(!string.IsNullOrEmpty(tex?.ResolvedClass) ? tex!.ResolvedClass : BaseSkin(v));
|
|
||||||
sb.Append(head);
|
|
||||||
prevKey = null;
|
prevKey = null;
|
||||||
run = 0;
|
run = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string key = !string.IsNullOrEmpty(tex?.ResolvedClass) ? tex!.ResolvedClass
|
string key = VehicleLabel(v);
|
||||||
: (!string.IsNullOrWhiteSpace(v.Name) ? v.Name! : BaseSkin(v));
|
|
||||||
|
|
||||||
if (string.Equals(key, prevKey, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(key, prevKey, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -69,6 +62,18 @@ public static class TrainsetDisplay
|
|||||||
!string.IsNullOrWhiteSpace(v.Name) ? v.Name! : BaseSkin(v)));
|
!string.IsNullOrWhiteSpace(v.Name) ? v.Name! : BaseSkin(v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string VehicleLabel(Dynamic v, bool head = false)
|
||||||
|
{
|
||||||
|
var tex = GameData.Instance.Vehicles.TextureForSkin(v.SkinFile);
|
||||||
|
string? cls = string.IsNullOrEmpty(tex?.ResolvedClass) ? null : tex!.ResolvedClass;
|
||||||
|
string? name = string.IsNullOrWhiteSpace(v.Name) ? null : v.Name;
|
||||||
|
return (head ? name ?? cls : cls ?? name) ?? BaseSkin(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsPowered(Dynamic v) =>
|
||||||
|
GameData.Instance.Vehicles.TextureForSkin(v.SkinFile)?.ResolvedCategory
|
||||||
|
is "e" or "s" or "p" or "z" or "a";
|
||||||
|
|
||||||
private static string BaseSkin(Dynamic v) =>
|
private static string BaseSkin(Dynamic v) =>
|
||||||
string.IsNullOrWhiteSpace(v.SkinFile) ? "?" :
|
string.IsNullOrWhiteSpace(v.SkinFile) ? "?" :
|
||||||
System.IO.Path.GetFileNameWithoutExtension(v.SkinFile);
|
System.IO.Path.GetFileNameWithoutExtension(v.SkinFile);
|
||||||
@@ -87,6 +92,33 @@ public static class TrainsetDisplay
|
|||||||
return staffed?.Name ?? trainset.Vehicles[0].Name;
|
return staffed?.Name ?? trainset.Vehicles[0].Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<(string Label, string Value)> StatsFields(
|
||||||
|
Trainset? trainset, Func<Dynamic, Physics?> physicsFor,
|
||||||
|
Func<Dynamic, string?>? categoryOf = null,
|
||||||
|
Func<string?, int>? loadWeightKg = null)
|
||||||
|
{
|
||||||
|
var fields = new List<(string, string)>();
|
||||||
|
if (trainset is null)
|
||||||
|
return fields;
|
||||||
|
|
||||||
|
var (lengthM, massKg, loadKg) = RecalcParams(trainset, physicsFor, categoryOf, loadWeightKg, allVehicles: false);
|
||||||
|
if (massKg < 200_000)
|
||||||
|
(lengthM, massKg, loadKg) = RecalcParams(trainset, physicsFor, categoryOf, loadWeightKg, allVehicles: true);
|
||||||
|
|
||||||
|
var inv = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
string track = trainset.Track ?? "";
|
||||||
|
if (trainset.Velocity > 0)
|
||||||
|
track = $"{track} ({trainset.Velocity.ToString("0.#", inv)} km/h)";
|
||||||
|
|
||||||
|
fields.Add(($"{App.Loc["Mass"]} [t]:", (massKg / 1000.0).ToString("0.#", inv)));
|
||||||
|
fields.Add(($"{App.Loc["MassBrutto"]} [t]:", ((massKg + loadKg) / 1000.0).ToString("0.#", inv)));
|
||||||
|
fields.Add(($"{App.Loc["Length"]} [m]:", lengthM.ToString("0.#", inv)));
|
||||||
|
fields.Add(($"{App.Loc["VehicleCount"]}:", trainset.Vehicles.Count.ToString(inv)));
|
||||||
|
fields.Add(($"{App.Loc["Track"]}:", track));
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
public static string FormatStats(Trainset? trainset, Func<Dynamic, Physics?> physicsFor,
|
public static string FormatStats(Trainset? trainset, Func<Dynamic, Physics?> physicsFor,
|
||||||
string lengthLabel, string massLabel, string vehiclesLabel, string trackLabel,
|
string lengthLabel, string massLabel, string vehiclesLabel, string trackLabel,
|
||||||
Func<Dynamic, string?>? categoryOf = null,
|
Func<Dynamic, string?>? categoryOf = null,
|
||||||
@@ -154,7 +186,7 @@ public static class TrainsetDisplay
|
|||||||
private static bool LoadAccepted(Physics? p, string loadType)
|
private static bool LoadAccepted(Physics? p, string loadType)
|
||||||
{
|
{
|
||||||
if (p == null || string.IsNullOrWhiteSpace(p.LoadAccepted))
|
if (p == null || string.IsNullOrWhiteSpace(p.LoadAccepted))
|
||||||
return true;
|
return false;
|
||||||
return p.LoadAccepted.Contains(loadType, StringComparison.OrdinalIgnoreCase);
|
return p.LoadAccepted.Contains(loadType, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,20 +212,12 @@ public static class TrainsetDisplay
|
|||||||
if (mu)
|
if (mu)
|
||||||
{
|
{
|
||||||
foreach (var v in trainset.Vehicles)
|
foreach (var v in trainset.Vehicles)
|
||||||
{
|
Rename(v, used);
|
||||||
if (!string.IsNullOrEmpty(v.Name))
|
|
||||||
used.Remove(v.Name!);
|
|
||||||
v.Name = EnsureUnique(PreferredBaseName(v), used);
|
|
||||||
}
|
|
||||||
return target?.Name;
|
return target?.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
Rename(target, used);
|
||||||
if (!string.IsNullOrEmpty(target.Name))
|
|
||||||
used.Remove(target.Name!);
|
|
||||||
target.Name = EnsureUnique(PreferredBaseName(target), used);
|
|
||||||
}
|
|
||||||
return target?.Name;
|
return target?.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,4 +275,21 @@ public static class TrainsetDisplay
|
|||||||
|
|
||||||
private static string SanitizeName(string name) =>
|
private static string SanitizeName(string name) =>
|
||||||
string.IsNullOrWhiteSpace(name) ? "vehicle" : name.Replace(' ', '_');
|
string.IsNullOrWhiteSpace(name) ? "vehicle" : name.Replace(' ', '_');
|
||||||
|
|
||||||
|
public static (string Base, string Suffix) SplitWagonNumber(string? name)
|
||||||
|
{
|
||||||
|
string n = name ?? "";
|
||||||
|
int hash = n.LastIndexOf('#');
|
||||||
|
if (hash >= 0 && hash + 1 < n.Length && n[(hash + 1)..].All(char.IsDigit))
|
||||||
|
return (n[..hash], n[hash..]);
|
||||||
|
return (n, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Rename(Dynamic v, HashSet<string> used)
|
||||||
|
{
|
||||||
|
var (_, number) = SplitWagonNumber(v.Name);
|
||||||
|
if (!string.IsNullOrEmpty(v.Name))
|
||||||
|
used.Remove(v.Name!);
|
||||||
|
v.Name = EnsureUnique(PreferredBaseName(v), used) + number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
198
StarterNG/Domain/UData.cs
Normal file
198
StarterNG/Domain/UData.cs
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using StarterNG.Classes;
|
||||||
|
|
||||||
|
namespace StarterNG.Domain;
|
||||||
|
|
||||||
|
public static class UData
|
||||||
|
{
|
||||||
|
public readonly record struct TrainParams(double Length, double Mass, double LoadMass, int CountVehicles);
|
||||||
|
|
||||||
|
public static int GetMaxCoupler(Dynamic v, Func<Dynamic, Physics?> physicsFor, bool leftCoupler = true)
|
||||||
|
{
|
||||||
|
var fiz = physicsFor(v);
|
||||||
|
if (fiz == null) return 3;
|
||||||
|
return leftCoupler
|
||||||
|
? (v.Offset >= 0 ? fiz.AllowedFlagA : fiz.AllowedFlagB)
|
||||||
|
: (v.Offset >= 0 ? fiz.AllowedFlagB : fiz.AllowedFlagA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetControlType(Dynamic v, Func<Dynamic, Physics?> physicsFor, bool leftCoupler = true)
|
||||||
|
{
|
||||||
|
var fiz = physicsFor(v);
|
||||||
|
if (fiz == null) return "";
|
||||||
|
return leftCoupler
|
||||||
|
? (v.Offset >= 0 ? fiz.ControlTypeA : fiz.ControlTypeB)
|
||||||
|
: (v.Offset >= 0 ? fiz.ControlTypeB : fiz.ControlTypeA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<int> CheckFlag(int flag)
|
||||||
|
{
|
||||||
|
var result = new List<int>();
|
||||||
|
for (int f = 128; f >= 1; f >>= 1)
|
||||||
|
if (flag >= f) { result.Add(f); flag -= f; }
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CommonCoupler(int c1, int c2)
|
||||||
|
{
|
||||||
|
var f1 = CheckFlag(c1);
|
||||||
|
var f2 = CheckFlag(c2);
|
||||||
|
return f1.Where(f2.Contains).Sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AutoConnect(IList<Dynamic> vehicles, int leftVehicle,
|
||||||
|
Func<Dynamic, Physics?> physicsFor)
|
||||||
|
{
|
||||||
|
if (leftVehicle < 0 || vehicles.Count - 1 <= leftVehicle) return;
|
||||||
|
|
||||||
|
int leftMax = GetMaxCoupler(vehicles[leftVehicle], physicsFor, leftCoupler: false);
|
||||||
|
string controlType1 = GetControlType(vehicles[leftVehicle], physicsFor, leftCoupler: false);
|
||||||
|
|
||||||
|
int rightMax = GetMaxCoupler(vehicles[leftVehicle + 1], physicsFor);
|
||||||
|
string controlType2 = GetControlType(vehicles[leftVehicle + 1], physicsFor);
|
||||||
|
|
||||||
|
int coupler = CommonCoupler(leftMax, rightMax);
|
||||||
|
|
||||||
|
const int F4 = 4;
|
||||||
|
if (CheckFlag(coupler).Contains(F4) &&
|
||||||
|
!string.Equals(controlType1, controlType2, StringComparison.OrdinalIgnoreCase))
|
||||||
|
coupler -= F4;
|
||||||
|
|
||||||
|
vehicles[leftVehicle].Coupling.Flags = coupler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<int> GetMultiple(IList<Dynamic> vehicles, int index,
|
||||||
|
Func<Dynamic, string?> setIdOf)
|
||||||
|
{
|
||||||
|
var result = new List<int> { index };
|
||||||
|
|
||||||
|
bool Same(int a, int b)
|
||||||
|
{
|
||||||
|
string? x = setIdOf(vehicles[a]);
|
||||||
|
return x != null && x == setIdOf(vehicles[b]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (index - i > 0 && Same(index - i, index - i - 1))
|
||||||
|
{
|
||||||
|
result.Add(index - i - 1);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (vehicles.Count - 1 > index + i && Same(index + i, index + i + 1))
|
||||||
|
{
|
||||||
|
result.Add(index + i + 1);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool StaffedTrain(Trainset train) =>
|
||||||
|
train.Vehicles.Count > 0 &&
|
||||||
|
(IsDriver(train.Vehicles[0].DriverType) || IsDriver(train.Vehicles[^1].DriverType));
|
||||||
|
|
||||||
|
private static bool IsDriver(eDriverType d) =>
|
||||||
|
d is eDriverType.Headdriver or eDriverType.Reardriver;
|
||||||
|
|
||||||
|
public static bool IncludeVehicleToMass(Dynamic v, bool allVehicles,
|
||||||
|
Func<Dynamic, Physics?> physicsFor, Func<Dynamic, string?> categoryOf)
|
||||||
|
{
|
||||||
|
if (physicsFor(v) == null) return false;
|
||||||
|
if (allVehicles) return true;
|
||||||
|
|
||||||
|
bool notDriver = v.DriverType is eDriverType.Passenger or eDriverType.Nobody;
|
||||||
|
string? cat = categoryOf(v);
|
||||||
|
return notDriver || cat is "z" or "a";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TrainParams RecalcTrainParams(Trainset? train, bool allVehicles,
|
||||||
|
Func<Dynamic, Physics?> physicsFor, Func<Dynamic, string?> categoryOf,
|
||||||
|
Func<string?, int> loadWeight)
|
||||||
|
{
|
||||||
|
double length = 0, mass = 0, loadMass = 0;
|
||||||
|
if (train == null) return new TrainParams(0, 0, 0, 0);
|
||||||
|
|
||||||
|
for (int i = train.Vehicles.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var v = train.Vehicles[i];
|
||||||
|
var fiz = physicsFor(v);
|
||||||
|
|
||||||
|
if (IncludeVehicleToMass(v, allVehicles, physicsFor, categoryOf) && fiz != null)
|
||||||
|
{
|
||||||
|
mass += fiz.Mass;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(v.LoadType) &&
|
||||||
|
fiz.LoadAccepted.Contains(v.LoadType!, StringComparison.OrdinalIgnoreCase))
|
||||||
|
loadMass += v.LoadCount * loadWeight(v.LoadType);
|
||||||
|
else if (!string.IsNullOrEmpty(v.LoadType))
|
||||||
|
Infrastructure.Diagnostics.Log($"{v.Name} - nieobsługiwany ładunek pojazdu");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fiz != null)
|
||||||
|
length += fiz.Length;
|
||||||
|
}
|
||||||
|
return new TrainParams(length, mass, loadMass, train.Vehicles.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RandomLoad(IEnumerable<Dynamic> vehicles,
|
||||||
|
Func<Dynamic, Physics?> physicsFor, Random rng)
|
||||||
|
{
|
||||||
|
foreach (var v in vehicles)
|
||||||
|
{
|
||||||
|
var fiz = physicsFor(v);
|
||||||
|
if (fiz == null) continue;
|
||||||
|
|
||||||
|
var loads = fiz.LoadAccepted
|
||||||
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.Where(x => x.Length > 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (loads.Count > 0)
|
||||||
|
v.LoadType = loads[rng.Next(loads.Count)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReverseMultiple(IList<Dynamic> vehicles, int position,
|
||||||
|
Func<Dynamic, string?> setIdOf, Func<Dynamic, Physics?> physicsFor)
|
||||||
|
{
|
||||||
|
var indexes = GetMultiple(vehicles, position, setIdOf);
|
||||||
|
|
||||||
|
foreach (int i in indexes)
|
||||||
|
vehicles[i].Offset = vehicles[i].Offset >= 0 ? -1f : 0f;
|
||||||
|
|
||||||
|
indexes.Sort();
|
||||||
|
|
||||||
|
int first = indexes[0], last = indexes[^1];
|
||||||
|
for (int i = 0; i < indexes.Count / 2; i++)
|
||||||
|
(vehicles[first + i], vehicles[last - i]) = (vehicles[last - i], vehicles[first + i]);
|
||||||
|
|
||||||
|
foreach (int i in indexes)
|
||||||
|
AutoConnect(vehicles, i, physicsFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CheckMoveVehicle(IList<Dynamic> vehicles, int fromPos, int toPos,
|
||||||
|
Func<Dynamic, string?> setIdOf)
|
||||||
|
{
|
||||||
|
bool Same(int a, int b)
|
||||||
|
{
|
||||||
|
if (a < 0 || b < 0 || a >= vehicles.Count || b >= vehicles.Count) return false;
|
||||||
|
string? x = setIdOf(vehicles[a]);
|
||||||
|
return x != null && x == setIdOf(vehicles[b]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromPos < toPos)
|
||||||
|
{
|
||||||
|
if (toPos > 0 && toPos < vehicles.Count - 1 && Same(toPos, toPos + 1))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (toPos > 0 && Same(toPos, toPos - 1))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
153
StarterNG/Domain/UDepot.cs
Normal file
153
StarterNG/Domain/UDepot.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using StarterNG.Classes;
|
||||||
|
using StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
namespace StarterNG.Domain;
|
||||||
|
|
||||||
|
public static class UDepot
|
||||||
|
{
|
||||||
|
public sealed class DepotTrain
|
||||||
|
{
|
||||||
|
public string TrainName = "";
|
||||||
|
public List<Dynamic> Vehicles = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? FindDepotFile(string? path = null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
return File.Exists(path) ? path : null;
|
||||||
|
|
||||||
|
string root = Directory.GetCurrentDirectory();
|
||||||
|
foreach (string rel in new[] { Path.Combine("starter", "magazyn.ini"), "starter.ini", "RAINSTED.INI" })
|
||||||
|
{
|
||||||
|
string full = Path.Combine(root, rel);
|
||||||
|
if (File.Exists(full)) return full;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<DepotTrain> ReadDepot(string? path = null)
|
||||||
|
{
|
||||||
|
var result = new List<DepotTrain>();
|
||||||
|
string? file = FindDepotFile(path);
|
||||||
|
if (file == null) return result;
|
||||||
|
|
||||||
|
string text;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
text = File.ReadAllText(file, Encoding.GetEncoding(1250));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log($"Błąd parsowania magazynu {file}", ex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DepotTrain? current = null;
|
||||||
|
int line = 0;
|
||||||
|
|
||||||
|
foreach (string raw in text.Replace("\r\n", "\n").Split('\n'))
|
||||||
|
{
|
||||||
|
line++;
|
||||||
|
string s = raw.Trim();
|
||||||
|
if (s.Length == 0) continue;
|
||||||
|
|
||||||
|
var header = Regex.Match(s, @"^\[\s*TRAINSET\d+\s*(?:=\s*([^\]]*))?\]", RegexOptions.IgnoreCase);
|
||||||
|
if (header.Success)
|
||||||
|
{
|
||||||
|
current = new DepotTrain { TrainName = header.Groups[1].Value.Trim() };
|
||||||
|
result.Add(current);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current == null) continue;
|
||||||
|
|
||||||
|
var entry = Regex.Match(s, @"^\d+\s*=\s*(.*)$");
|
||||||
|
string body = entry.Success ? entry.Groups[1].Value.Trim() : s;
|
||||||
|
if (!body.StartsWith("node", StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var v = ParseNode(body);
|
||||||
|
if (v != null) current.Vehicles.Add(v);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log($"Błąd parsowania magazynu. Linia: {line}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.RemoveAll(t => t.Vehicles.Count == 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dynamic? ParseNode(string body)
|
||||||
|
{
|
||||||
|
var t = body.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (t.Length < 10) return null;
|
||||||
|
|
||||||
|
var inv = CultureInfo.InvariantCulture;
|
||||||
|
int p = 1;
|
||||||
|
|
||||||
|
var d = new Dynamic
|
||||||
|
{
|
||||||
|
RangeMax = float.Parse(t[p++], inv),
|
||||||
|
RangeMin = float.Parse(t[p++], inv),
|
||||||
|
Name = t[p++]
|
||||||
|
};
|
||||||
|
p++;
|
||||||
|
d.DataFolder = t[p++];
|
||||||
|
d.SkinFile = Dynamic.StripSkinExtension(t[p++]);
|
||||||
|
d.MmdFile = t[p++];
|
||||||
|
d.Offset = float.Parse(t[p++], inv);
|
||||||
|
d.DriverType = t[p++].ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"headdriver" => eDriverType.Headdriver,
|
||||||
|
"reardriver" => eDriverType.Reardriver,
|
||||||
|
"passenger" => eDriverType.Passenger,
|
||||||
|
_ => eDriverType.Nobody
|
||||||
|
};
|
||||||
|
d.Coupling = Coupling.Parse(t[p++]);
|
||||||
|
|
||||||
|
var trailing = new List<string>();
|
||||||
|
while (p < t.Length && !string.Equals(t[p], "enddynamic", StringComparison.OrdinalIgnoreCase))
|
||||||
|
trailing.Add(t[p++]);
|
||||||
|
d.ReadTrailing(trailing);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool SaveDepot(IReadOnlyList<DepotTrain> depot, string? path = null)
|
||||||
|
{
|
||||||
|
path ??= Path.Combine(Directory.GetCurrentDirectory(), "starter", "magazyn.ini");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < depot.Count; i++)
|
||||||
|
{
|
||||||
|
if (depot[i].Vehicles.Count == 0) continue;
|
||||||
|
|
||||||
|
sb.Append('[').Append("TRAINSET").Append(i).Append('=')
|
||||||
|
.Append(depot[i].TrainName).Append("]\n");
|
||||||
|
|
||||||
|
for (int y = 0; y < depot[i].Vehicles.Count; y++)
|
||||||
|
sb.Append(y.ToString("00", CultureInfo.InvariantCulture)).Append('=')
|
||||||
|
.Append(depot[i].Vehicles[y].ToTrainsetNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
string? dir = Path.GetDirectoryName(path);
|
||||||
|
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
|
||||||
|
File.WriteAllText(path, sb.ToString(), Encoding.GetEncoding(1250));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log("Błąd zapisu magazynu", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
StarterNG/Infrastructure/Diagnostics.cs
Normal file
119
StarterNG/Infrastructure/Diagnostics.cs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using StarterNG.Views;
|
||||||
|
|
||||||
|
namespace StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
public static class Diagnostics
|
||||||
|
{
|
||||||
|
private static readonly object Gate = new();
|
||||||
|
private static readonly List<string> Lines = new();
|
||||||
|
|
||||||
|
public static string LogPath =>
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "starter", "bledy.txt");
|
||||||
|
|
||||||
|
public static string Text
|
||||||
|
{
|
||||||
|
get { lock (Gate) return string.Join(Environment.NewLine, Lines); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Clear()
|
||||||
|
{
|
||||||
|
lock (Gate) Lines.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(string message)
|
||||||
|
{
|
||||||
|
lock (Gate)
|
||||||
|
Lines.Add($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)} {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(IEnumerable<string> messages)
|
||||||
|
{
|
||||||
|
foreach (string message in messages)
|
||||||
|
Log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(string context, Exception ex) =>
|
||||||
|
Log($"{context}: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
|
||||||
|
public static void Flush()
|
||||||
|
{
|
||||||
|
string[] snapshot;
|
||||||
|
lock (Gate)
|
||||||
|
{
|
||||||
|
if (Lines.Count == 0) return;
|
||||||
|
snapshot = Lines.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dir = Path.GetDirectoryName(LogPath)!;
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
File.WriteAllLines(LogPath, snapshot);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Window? Owner =>
|
||||||
|
(Avalonia.Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)
|
||||||
|
?.MainWindow;
|
||||||
|
|
||||||
|
public static Task ReportAsync(string message, string? title = null)
|
||||||
|
{
|
||||||
|
Log(message.Replace(Environment.NewLine, " | "));
|
||||||
|
|
||||||
|
var owner = Owner;
|
||||||
|
if (owner is null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
return MessageBox.Show(owner, message, title ?? App.Loc["FaultTitle"], MessageBoxButtons.Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task ReportAsync(string context, Exception ex, string? title = null) =>
|
||||||
|
ReportAsync($"{context}{Environment.NewLine}{Environment.NewLine}{App.Loc["FaultDetail"]} {ex.Message}", title);
|
||||||
|
|
||||||
|
public static void ReportOnUiThread(string message, string? title = null)
|
||||||
|
{
|
||||||
|
if (Dispatcher.UIThread.CheckAccess())
|
||||||
|
_ = ReportAsync(message, title);
|
||||||
|
else
|
||||||
|
Dispatcher.UIThread.Post(() => _ = ReportAsync(message, title));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> CheckInstallation()
|
||||||
|
{
|
||||||
|
var faults = new List<string>();
|
||||||
|
string root = Directory.GetCurrentDirectory();
|
||||||
|
|
||||||
|
foreach (string folder in new[] { "dynamic", "sounds", "models", "scenery", "textures" })
|
||||||
|
if (!Directory.Exists(Path.Combine(root, folder)))
|
||||||
|
faults.Add(string.Format(App.Loc["FaultNoDir"], "/" + folder));
|
||||||
|
|
||||||
|
if (!File.Exists(Path.Combine(root, "data", "load_weights.txt")))
|
||||||
|
faults.Add(App.Loc["FaultNoWeights"]);
|
||||||
|
|
||||||
|
if (Classes.GameData.Instance.Sceneries.Count == 0)
|
||||||
|
faults.Add(App.Loc["FaultNoScenery"]);
|
||||||
|
|
||||||
|
if (Classes.GameData.Instance.Vehicles.Textures.Count == 0)
|
||||||
|
faults.Add(App.Loc["FaultNoVehicles"]);
|
||||||
|
|
||||||
|
if (Classes.Physics.IndexedCount == 0)
|
||||||
|
faults.Add(App.Loc["FaultNoPhysics"]);
|
||||||
|
|
||||||
|
if (Classes.Settings.Instance.ResolveExecutable(out var problem) is var _ &&
|
||||||
|
problem == Classes.ExeProblem.NotFound)
|
||||||
|
faults.Add(App.Loc["FaultNoExe"]);
|
||||||
|
|
||||||
|
return faults;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
StarterNG/Infrastructure/DropGap.cs
Normal file
67
StarterNG/Infrastructure/DropGap.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia.Animation;
|
||||||
|
using Avalonia.Animation.Easings;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
|
||||||
|
namespace StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
public sealed class DropGap
|
||||||
|
{
|
||||||
|
private readonly Border _gap;
|
||||||
|
private int _index = -1;
|
||||||
|
|
||||||
|
public DropGap(int milliseconds = 130)
|
||||||
|
{
|
||||||
|
_gap = new Border
|
||||||
|
{
|
||||||
|
Width = 0,
|
||||||
|
IsHitTestVisible = false,
|
||||||
|
Transitions = new Transitions
|
||||||
|
{
|
||||||
|
new DoubleTransition
|
||||||
|
{
|
||||||
|
Property = Layoutable.WidthProperty,
|
||||||
|
Duration = TimeSpan.FromMilliseconds(milliseconds),
|
||||||
|
Easing = new CubicEaseOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsGap(Control control) => ReferenceEquals(control, _gap);
|
||||||
|
|
||||||
|
public void Show(Panel panel, int childIndex, double width)
|
||||||
|
{
|
||||||
|
int target = Math.Clamp(childIndex, 0, ChildCountWithoutGap(panel));
|
||||||
|
|
||||||
|
if (_index != target)
|
||||||
|
{
|
||||||
|
panel.Children.Remove(_gap);
|
||||||
|
panel.Children.Insert(Math.Clamp(target, 0, panel.Children.Count), _gap);
|
||||||
|
_index = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gap.Width = Math.Max(width, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Hide(Panel panel)
|
||||||
|
{
|
||||||
|
if (_index < 0) return;
|
||||||
|
|
||||||
|
_gap.Width = 0;
|
||||||
|
panel.Children.Remove(_gap);
|
||||||
|
_index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Forget() => _index = -1;
|
||||||
|
|
||||||
|
private int ChildCountWithoutGap(Panel panel)
|
||||||
|
{
|
||||||
|
int count = panel.Children.Count;
|
||||||
|
foreach (var child in panel.Children)
|
||||||
|
if (ReferenceEquals(child, _gap))
|
||||||
|
return count - 1;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
StarterNG/Infrastructure/DropIndexTracker.cs
Normal file
59
StarterNG/Infrastructure/DropIndexTracker.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
public sealed class DropIndexTracker
|
||||||
|
{
|
||||||
|
private const double Hysteresis = 14;
|
||||||
|
private static readonly TimeSpan Quiet = TimeSpan.FromMilliseconds(60);
|
||||||
|
|
||||||
|
private readonly List<double> _midpoints = new();
|
||||||
|
private DateTime _changedAt = DateTime.MinValue;
|
||||||
|
|
||||||
|
public int Index { get; private set; } = -1;
|
||||||
|
|
||||||
|
public void Capture(IEnumerable<double> midpoints)
|
||||||
|
{
|
||||||
|
_midpoints.Clear();
|
||||||
|
_midpoints.AddRange(midpoints);
|
||||||
|
Index = -1;
|
||||||
|
_changedAt = DateTime.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_midpoints.Clear();
|
||||||
|
Index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Update(double x)
|
||||||
|
{
|
||||||
|
int candidate = Index;
|
||||||
|
|
||||||
|
if (candidate < 0)
|
||||||
|
{
|
||||||
|
candidate = 0;
|
||||||
|
while (candidate < _midpoints.Count && x > _midpoints[candidate])
|
||||||
|
candidate++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (candidate < _midpoints.Count && x > _midpoints[candidate] + Hysteresis)
|
||||||
|
candidate++;
|
||||||
|
while (candidate > 0 && x < _midpoints[candidate - 1] - Hysteresis)
|
||||||
|
candidate--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate == Index)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (Index >= 0 && now - _changedAt < Quiet)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_changedAt = now;
|
||||||
|
Index = candidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
StarterNG/Infrastructure/EdgeScroller.cs
Normal file
63
StarterNG/Infrastructure/EdgeScroller.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
namespace StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
public sealed class EdgeScroller
|
||||||
|
{
|
||||||
|
private const double Zone = 56;
|
||||||
|
private const double MaxStep = 18;
|
||||||
|
|
||||||
|
private readonly DispatcherTimer _timer;
|
||||||
|
private ScrollViewer? _target;
|
||||||
|
private double _step;
|
||||||
|
|
||||||
|
public EdgeScroller()
|
||||||
|
{
|
||||||
|
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(16) };
|
||||||
|
_timer.Tick += (_, _) => Step();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(ScrollViewer scroll, double pointerX)
|
||||||
|
{
|
||||||
|
double width = scroll.Viewport.Width;
|
||||||
|
|
||||||
|
_step = pointerX < Zone
|
||||||
|
? -MaxStep * Math.Clamp((Zone - pointerX) / Zone, 0, 1)
|
||||||
|
: pointerX > width - Zone
|
||||||
|
? MaxStep * Math.Clamp((pointerX - (width - Zone)) / Zone, 0, 1)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
_target = scroll;
|
||||||
|
|
||||||
|
if (_step == 0)
|
||||||
|
_timer.Stop();
|
||||||
|
else if (!_timer.IsEnabled)
|
||||||
|
_timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
_target = null;
|
||||||
|
_step = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Step()
|
||||||
|
{
|
||||||
|
if (_target is null || _step == 0)
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = Math.Max(0, _target.Extent.Width - _target.Viewport.Width);
|
||||||
|
double x = Math.Clamp(_target.Offset.X + _step, 0, max);
|
||||||
|
if (Math.Abs(x - _target.Offset.X) < 0.01)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_target.Offset = new Vector(x, _target.Offset.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,9 +29,9 @@ public static class LoadingScreen
|
|||||||
|
|
||||||
WriteBmp(src, dest);
|
WriteBmp(src, dest);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Diagnostics.Log("Loading screen logo", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
StarterNG/Infrastructure/StatsBar.cs
Normal file
40
StarterNG/Infrastructure/StatsBar.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var pair = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
Spacing = 5,
|
||||||
|
Margin = new Avalonia.Thickness(0, 0, 22, 0),
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
};
|
||||||
|
pair.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = label,
|
||||||
|
FontSize = 12,
|
||||||
|
Opacity = 0.65,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
});
|
||||||
|
pair.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = value,
|
||||||
|
FontSize = 12,
|
||||||
|
FontWeight = FontWeight.SemiBold,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
TextTrimming = TextTrimming.CharacterEllipsis
|
||||||
|
});
|
||||||
|
host.Children.Add(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,36 +10,21 @@
|
|||||||
xmlns:starterNg="clr-namespace:StarterNG"
|
xmlns:starterNg="clr-namespace:StarterNG"
|
||||||
Title="Starter MaSzyna"
|
Title="Starter MaSzyna"
|
||||||
Icon="/Assets/starter.ico"
|
Icon="/Assets/starter.ico"
|
||||||
Width="1200" Height="768" MinWidth="1200" MinHeight="666"
|
Width="1010" Height="920" MinWidth="1010" MinHeight="920"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen">
|
||||||
<!-- The client area is extended over the title bar in code-behind (the
|
|
||||||
ExtendClientArea* hints), so the logo + nav sit on the top bar while the
|
|
||||||
standard minimise / maximise / close buttons stay in the top-right corner.
|
|
||||||
Extending the client area also makes Avalonia 12 add an extra full-screen caption
|
|
||||||
button; it is hidden in App.axaml via its WindowDecorationProperties.ElementRole. -->
|
|
||||||
|
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
|
|
||||||
<!-- ── Top bar: logo, navigation and the right cluster in one row.
|
|
||||||
On Windows the OS caption buttons are drawn over the top of the
|
|
||||||
extended client area, so TopBar is padded down to clear them
|
|
||||||
(see the constructor); the free strip drags the window. -->
|
|
||||||
<Border x:Name="TopBar" DockPanel.Dock="Top"
|
<Border x:Name="TopBar" DockPanel.Dock="Top"
|
||||||
Background="{StaticResource CarbonPanelBg}"
|
Background="{StaticResource CarbonPanelBg}"
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||||
BorderThickness="0,0,0,1"
|
BorderThickness="0,0,0,1"
|
||||||
PointerPressed="TitleBar_OnPointerPressed">
|
PointerPressed="TitleBar_OnPointerPressed">
|
||||||
<Grid RowDefinitions="48">
|
<Grid RowDefinitions="50">
|
||||||
|
|
||||||
<!-- Logo + navigation (left) and the right cluster (right) -->
|
<Grid ColumnDefinitions="Auto,*,Auto" Background="Transparent">
|
||||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto" Background="Transparent">
|
|
||||||
|
|
||||||
<!-- Logo -->
|
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
||||||
<Image Grid.Column="0" Source="/Assets/Logo/maszyna_white.png"
|
|
||||||
Height="34" Margin="14,0,18,0" VerticalAlignment="Center" />
|
|
||||||
|
|
||||||
<!-- Page tabs -->
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Stretch">
|
|
||||||
<RadioButton x:Name="NavScenarios" GroupName="nav" Theme="{StaticResource NavTab}"
|
<RadioButton x:Name="NavScenarios" GroupName="nav" Theme="{StaticResource NavTab}"
|
||||||
Tag="scenarios" IsChecked="True"
|
Tag="scenarios" IsChecked="True"
|
||||||
IsCheckedChanged="Nav_OnCheckedChanged"
|
IsCheckedChanged="Nav_OnCheckedChanged"
|
||||||
@@ -64,22 +49,18 @@
|
|||||||
HorizontalContentAlignment="Center" />
|
HorizontalContentAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Right cluster: version, language, social links. -->
|
<StackPanel Grid.Column="2" Orientation="Horizontal"
|
||||||
<StackPanel Grid.Column="3" Orientation="Horizontal"
|
VerticalAlignment="Center" Margin="0,0,3,0" Spacing="0">
|
||||||
VerticalAlignment="Center" Margin="0,0,8,0" Spacing="4">
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="0,0,6,0">
|
||||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="0,0,8,0">
|
|
||||||
<Label Content="{Binding [Version], Source={x:Static starterNg:App.Loc}}"
|
<Label Content="{Binding [Version], Source={x:Static starterNg:App.Loc}}"
|
||||||
FontSize="10" Foreground="{StaticResource CarbonFgDim}" Padding="0" />
|
FontSize="10" Foreground="{StaticResource CarbonFgDim}" Padding="0" />
|
||||||
<Label Content="{x:Static starterNg:AppInfo.Version}"
|
<Label Content="{x:Static starterNg:AppInfo.Version}"
|
||||||
FontSize="10" Foreground="{StaticResource CarbonFg}" Padding="0" />
|
FontSize="10" Foreground="{StaticResource CarbonFg}" Padding="0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ComboBox x:Name="LangCombo" Width="110" VerticalAlignment="Center"
|
<ComboBox x:Name="LangCombo" Width="88" VerticalAlignment="Center"
|
||||||
SelectionChanged="LangCombo_OnSelectionChanged" />
|
SelectionChanged="LangCombo_OnSelectionChanged" />
|
||||||
|
|
||||||
<Button Classes="Basic" Cursor="Hand" Tag="http://eu07.pl/" Click="linkClick">
|
|
||||||
<materialIcons:MaterialIcon Kind="Web" />
|
|
||||||
</Button>
|
|
||||||
<Button Classes="Basic" Cursor="Hand" Tag="https://discord.gg/eu07" Click="linkClick">
|
<Button Classes="Basic" Cursor="Hand" Tag="https://discord.gg/eu07" Click="linkClick">
|
||||||
<PathIcon Data="{StaticResource DiscordLogo}" Height="12" />
|
<PathIcon Data="{StaticResource DiscordLogo}" Height="12" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -92,18 +73,22 @@
|
|||||||
<Button Classes="Basic" Cursor="Hand" Tag="http://github.com/MaSzyna-EU07/" Click="linkClick">
|
<Button Classes="Basic" Cursor="Hand" Tag="http://github.com/MaSzyna-EU07/" Click="linkClick">
|
||||||
<materialIcons:MaterialIcon Kind="Github" />
|
<materialIcons:MaterialIcon Kind="Github" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="Basic" Cursor="Hand" Tag="http://eu07.pl/" Click="linkClick"
|
||||||
|
Padding="6,0" Margin="4,0,0,0"
|
||||||
|
ToolTip.Tip="eu07.pl">
|
||||||
|
<Image Source="/Assets/Logo/maszyna_white.png" Height="26" />
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- ── Full-width START button (bottom) ─────────────────────────────── -->
|
<Button Name="startButton" DockPanel.Dock="Bottom" Classes="Start" Height="42" Margin="3"
|
||||||
<Button Name="startButton" DockPanel.Dock="Bottom" Classes="Start" Height="44" Margin="6"
|
|
||||||
Click="StartButton_OnClick"
|
Click="StartButton_OnClick"
|
||||||
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}" />
|
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
|
||||||
<!-- ── Page host (one view visible at a time, like the original PageControl) -->
|
<Panel Margin="3,3,3,0">
|
||||||
<Panel Margin="6,6,6,0">
|
|
||||||
<views:Scenarios x:Name="ScenariosView" />
|
<views:Scenarios x:Name="ScenariosView" />
|
||||||
<views:Depot x:Name="DepotView" IsVisible="False" />
|
<views:Depot x:Name="DepotView" IsVisible="False" />
|
||||||
<views:Settings x:Name="SettingsView" IsVisible="False" />
|
<views:Settings x:Name="SettingsView" IsVisible="False" />
|
||||||
|
|||||||
@@ -40,9 +40,24 @@ public partial class MainWindow : Window
|
|||||||
PopulateLangCombo();
|
PopulateLangCombo();
|
||||||
App.Loc.LanguageChanged += OnLanguageChanged;
|
App.Loc.LanguageChanged += OnLanguageChanged;
|
||||||
|
|
||||||
|
startButton.AddHandler(PointerReleasedEvent, StartButton_OnRightClick,
|
||||||
|
RoutingStrategies.Tunnel);
|
||||||
|
|
||||||
|
ScenariosView.RandomizeTexturesRequested += async scenery =>
|
||||||
|
{
|
||||||
|
await DepotView.RandomizeSceneryTrainsetsAsync(scenery);
|
||||||
|
ScenariosView.RefreshConsistView();
|
||||||
|
};
|
||||||
|
|
||||||
|
NavSettings.AddHandler(PointerReleasedEvent, NavSettings_OnRightClick,
|
||||||
|
RoutingStrategies.Tunnel);
|
||||||
|
|
||||||
|
ApplyInitialNav();
|
||||||
|
|
||||||
AppState.Instance.Changed += UpdateStartButton;
|
AppState.Instance.Changed += UpdateStartButton;
|
||||||
Opened += (_, _) =>
|
Opened += (_, _) =>
|
||||||
{
|
{
|
||||||
|
|
||||||
_openedUtc = DateTime.UtcNow;
|
_openedUtc = DateTime.UtcNow;
|
||||||
UpdateStartButton();
|
UpdateStartButton();
|
||||||
};
|
};
|
||||||
@@ -55,6 +70,25 @@ public partial class MainWindow : Window
|
|||||||
BeginMoveDrag(e);
|
BeginMoveDrag(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NavSettings_OnRightClick(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.InitialPressMouseButton != MouseButton.Right)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var win = new Views.AdvancedSettingsWindow();
|
||||||
|
win.ToolRequested += tool =>
|
||||||
|
{
|
||||||
|
switch (tool)
|
||||||
|
{
|
||||||
|
case "category": DepotView.ToolAddAllCategory(); break;
|
||||||
|
case "mmd": DepotView.ToolAddUniqueMmd(); break;
|
||||||
|
case "removeall": DepotView.ToolRemoveAllTrains(); break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
win.ShowDialog(this);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void Nav_OnCheckedChanged(object? sender, RoutedEventArgs e)
|
private void Nav_OnCheckedChanged(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not RadioButton { IsChecked: true } rb)
|
if (sender is not RadioButton { IsChecked: true } rb)
|
||||||
@@ -63,12 +97,28 @@ public partial class MainWindow : Window
|
|||||||
if (ScenariosView is null || DepotView is null || SettingsView is null)
|
if (ScenariosView is null || DepotView is null || SettingsView is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string page = rb.Tag as string ?? "scenarios";
|
ShowPage(rb.Tag as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowPage(string? page)
|
||||||
|
{
|
||||||
|
page ??= "scenarios";
|
||||||
ScenariosView.IsVisible = page == "scenarios";
|
ScenariosView.IsVisible = page == "scenarios";
|
||||||
DepotView.IsVisible = page == "depot";
|
DepotView.IsVisible = page == "depot";
|
||||||
SettingsView.IsVisible = page == "settings";
|
SettingsView.IsVisible = page == "settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyInitialNav()
|
||||||
|
{
|
||||||
|
foreach (var rb in new[] { NavScenarios, NavDepot, NavSettings })
|
||||||
|
if (rb.IsChecked == true)
|
||||||
|
{
|
||||||
|
ShowPage(rb.Tag as string);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ShowPage("scenarios");
|
||||||
|
}
|
||||||
|
|
||||||
private void PopulateLangCombo()
|
private void PopulateLangCombo()
|
||||||
{
|
{
|
||||||
LangCombo.Items.Clear();
|
LangCombo.Items.Clear();
|
||||||
@@ -163,22 +213,35 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
target = Path.GetFullPath(pathOrUrl);
|
target = Path.GetFullPath(pathOrUrl);
|
||||||
if (!File.Exists(target) && !Directory.Exists(target))
|
if (!File.Exists(target) && !Directory.Exists(target))
|
||||||
|
{
|
||||||
|
Diagnostics.ReportOnUiThread(string.Format(App.Loc["FaultFileNotFound"], target));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Process.Start(new ProcessStartInfo(target) { UseShellExecute = true });
|
Process.Start(new ProcessStartInfo(target) { UseShellExecute = true });
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log($"Open {pathOrUrl}", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openLastLogClick(object? sender, RoutedEventArgs e)
|
private async void openLastLogClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
string logPath = "log.txt";
|
string logPath = Path.GetFullPath("log.txt");
|
||||||
if (File.Exists(logPath))
|
if (!File.Exists(logPath))
|
||||||
{
|
{
|
||||||
Process.Start(new ProcessStartInfo(logPath)
|
await Diagnostics.ReportAsync(string.Format(App.Loc["FaultFileNotFound"], logPath));
|
||||||
{
|
return;
|
||||||
UseShellExecute = true
|
}
|
||||||
});
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(logPath) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Diagnostics.ReportAsync(logPath, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +299,28 @@ public partial class MainWindow : Window
|
|||||||
App.Loc["ExeLaunchFailed"], MessageBoxButtons.Ok);
|
App.Loc["ExeLaunchFailed"], MessageBoxButtons.Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void StartButton_OnClick(object? sender, RoutedEventArgs e)
|
private async void StartButton_OnClick(object? sender, RoutedEventArgs e) =>
|
||||||
|
await LaunchAsync(saveSettings: true, freeFly: false);
|
||||||
|
|
||||||
|
private void StartButton_OnRightClick(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.InitialPressMouseButton != MouseButton.Right)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var menu = new ContextMenu();
|
||||||
|
|
||||||
|
var noSave = new MenuItem { Header = App.Loc["StartWithoutSaving"] };
|
||||||
|
noSave.Click += async (_, _) => await LaunchAsync(saveSettings: false, freeFly: false);
|
||||||
|
menu.Items.Add(noSave);
|
||||||
|
|
||||||
|
var freeFly = new MenuItem { Header = App.Loc["StartFreeFly"] };
|
||||||
|
freeFly.Click += async (_, _) => await LaunchAsync(saveSettings: true, freeFly: true);
|
||||||
|
menu.Items.Add(freeFly);
|
||||||
|
|
||||||
|
menu.Open(startButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LaunchAsync(bool saveSettings, bool freeFly)
|
||||||
{
|
{
|
||||||
var scenery = AppState.Instance.CurrentScenery;
|
var scenery = AppState.Instance.CurrentScenery;
|
||||||
if (scenery is null)
|
if (scenery is null)
|
||||||
@@ -245,7 +329,7 @@ public partial class MainWindow : Window
|
|||||||
if (trainset is null)
|
if (trainset is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!HasStartableVehicle(trainset) &&
|
if (!freeFly && !HasStartableVehicle(trainset) &&
|
||||||
!await MessageBox.Show(this, App.Loc["StartNoStaffConfirm"],
|
!await MessageBox.Show(this, App.Loc["StartNoStaffConfirm"],
|
||||||
App.Loc["StartNoStaffTitle"], MessageBoxButtons.YesNo))
|
App.Loc["StartNoStaffTitle"], MessageBoxButtons.YesNo))
|
||||||
return;
|
return;
|
||||||
@@ -277,12 +361,14 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
File.WriteAllText(exportPath, scenery.BuildExportContent(SettingsModel.Instance.IgnoreIrrelevantTrains), Encoding.GetEncoding(1250));
|
File.WriteAllText(exportPath, scenery.BuildExportContent(SettingsModel.Instance.IgnoreIrrelevantTrains), Encoding.GetEncoding(1250));
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
await Diagnostics.ReportAsync($"{exportPath}", ex, App.Loc["FaultTitle"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsModel.Instance.CaptureAndSave();
|
if (saveSettings)
|
||||||
|
SettingsModel.Instance.CaptureAndSave();
|
||||||
|
|
||||||
string exe = Path.GetFullPath(SettingsModel.Instance.ResolveExecutable(out var exeProblem));
|
string exe = Path.GetFullPath(SettingsModel.Instance.ResolveExecutable(out var exeProblem));
|
||||||
if (exeProblem != ExeProblem.None)
|
if (exeProblem != ExeProblem.None)
|
||||||
@@ -297,7 +383,7 @@ public partial class MainWindow : Window
|
|||||||
sim = Process.Start(new ProcessStartInfo
|
sim = Process.Start(new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = exe,
|
FileName = exe,
|
||||||
Arguments = $"-s {exportName} -v {vehicle}",
|
Arguments = freeFly ? $"-s {exportName}" : $"-s {exportName} -v {vehicle}",
|
||||||
WorkingDirectory = Path.GetDirectoryName(exe) ?? Directory.GetCurrentDirectory(),
|
WorkingDirectory = Path.GetDirectoryName(exe) ?? Directory.GetCurrentDirectory(),
|
||||||
UseShellExecute = false
|
UseShellExecute = false
|
||||||
});
|
});
|
||||||
@@ -315,8 +401,6 @@ public partial class MainWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Hide();
|
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
GC.WaitForPendingFinalizers();
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
@@ -395,8 +479,9 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private void RestoreFromSimulator()
|
private void RestoreFromSimulator()
|
||||||
{
|
{
|
||||||
Show();
|
if (!IsVisible)
|
||||||
WindowState = WindowState.Normal;
|
Show();
|
||||||
Activate();
|
if (WindowState == WindowState.Minimized)
|
||||||
|
WindowState = WindowState.Normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using StarterNG.Infrastructure;
|
||||||
|
|
||||||
namespace StarterNG;
|
namespace StarterNG;
|
||||||
|
|
||||||
@@ -7,8 +9,35 @@ class Program
|
|||||||
{
|
{
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
public static void Main(string[] args)
|
||||||
.StartWithClassicDesktopLifetime(args);
|
{
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.ExceptionObject is Exception ex)
|
||||||
|
Diagnostics.Log("Unhandled exception", ex);
|
||||||
|
Diagnostics.Flush();
|
||||||
|
};
|
||||||
|
|
||||||
|
TaskScheduler.UnobservedTaskException += (_, e) =>
|
||||||
|
{
|
||||||
|
Diagnostics.Log("Unobserved task exception", e.Exception);
|
||||||
|
e.SetObserved();
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log("Fatal", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Diagnostics.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<ApplicationIcon>Assets\starter.ico</ApplicationIcon>
|
<ApplicationIcon>Assets\starter.ico</ApplicationIcon>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<AssemblyVersion>1.2.0.4</AssemblyVersion>
|
<AssemblyVersion>1.3.0.0</AssemblyVersion>
|
||||||
<FileVersion>1.2.0.4</FileVersion>
|
<FileVersion>1.3.0.0</FileVersion>
|
||||||
<AssemblyName>Starter</AssemblyName>
|
<AssemblyName>Starter</AssemblyName>
|
||||||
<Company>eu07.pl</Company>
|
<Company>eu07.pl</Company>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<!--
|
|
||||||
Carbon theme for StarterNG.
|
|
||||||
|
|
||||||
Recreates the dark "Carbon" VCL look of the original Delphi Starter without
|
|
||||||
SukiUI: a flat charcoal palette, square bordered group panels, top tab-style
|
|
||||||
navigation and a full-width START button. Plain Avalonia controls only, so
|
|
||||||
every view's code-behind keeps working unchanged.
|
|
||||||
-->
|
|
||||||
<Styles.Resources>
|
<Styles.Resources>
|
||||||
<!-- ── Palette ──────────────────────────────────────────────────── -->
|
|
||||||
<Color x:Key="CarbonAppBgColor">#15171B</Color>
|
<Color x:Key="CarbonAppBgColor">#15171B</Color>
|
||||||
<Color x:Key="CarbonPanelBgColor">#1E2327</Color>
|
<Color x:Key="CarbonPanelBgColor">#1E2327</Color>
|
||||||
<Color x:Key="CarbonHeaderBgColor">#2A3036</Color>
|
<Color x:Key="CarbonHeaderBgColor">#2A3036</Color>
|
||||||
@@ -25,14 +16,10 @@
|
|||||||
<SolidColorBrush x:Key="CarbonBorderDark" Color="#0E0F12" />
|
<SolidColorBrush x:Key="CarbonBorderDark" Color="#0E0F12" />
|
||||||
<SolidColorBrush x:Key="CarbonFg" Color="#E6E8EA" />
|
<SolidColorBrush x:Key="CarbonFg" Color="#E6E8EA" />
|
||||||
<SolidColorBrush x:Key="CarbonFgDim" Color="#9098A0" />
|
<SolidColorBrush x:Key="CarbonFgDim" Color="#9098A0" />
|
||||||
<!-- Accents in MaSzyna green: a readable dark green for fills, bright green
|
|
||||||
for hover/highlights. -->
|
|
||||||
<SolidColorBrush x:Key="CarbonAccent" Color="#177f00" />
|
<SolidColorBrush x:Key="CarbonAccent" Color="#177f00" />
|
||||||
<SolidColorBrush x:Key="CarbonAccentHover" Color="#41c400" />
|
<SolidColorBrush x:Key="CarbonAccentHover" Color="#41c400" />
|
||||||
<SolidColorBrush x:Key="CarbonAccentBright" Color="#41c400" />
|
<SolidColorBrush x:Key="CarbonAccentBright" Color="#41c400" />
|
||||||
|
|
||||||
<!-- ── GroupBox (replaces SukiUI GroupBox) ──────────────────────────
|
|
||||||
Use on a HeaderedContentControl via Theme="{StaticResource GroupBox}". -->
|
|
||||||
<ControlTheme x:Key="GroupBox" TargetType="HeaderedContentControl">
|
<ControlTheme x:Key="GroupBox" TargetType="HeaderedContentControl">
|
||||||
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
@@ -45,10 +32,16 @@
|
|||||||
Background="{StaticResource CarbonHeaderBg}"
|
Background="{StaticResource CarbonHeaderBg}"
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}"
|
BorderBrush="{StaticResource CarbonBorderLight}"
|
||||||
BorderThickness="0,0,0,1"
|
BorderThickness="0,0,0,1"
|
||||||
Padding="8,5">
|
Padding="8,4">
|
||||||
<TextBlock Text="{TemplateBinding Header}"
|
<DockPanel LastChildFill="True">
|
||||||
FontWeight="SemiBold"
|
<ContentPresenter DockPanel.Dock="Right"
|
||||||
Foreground="{StaticResource CarbonFg}" />
|
Content="{TemplateBinding Tag}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<TextBlock Text="{TemplateBinding Header}"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{StaticResource CarbonFg}" />
|
||||||
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<ContentPresenter Content="{TemplateBinding Content}"
|
<ContentPresenter Content="{TemplateBinding Content}"
|
||||||
Margin="8" />
|
Margin="8" />
|
||||||
@@ -58,13 +51,12 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
|
|
||||||
<!-- ── Top navigation tab (RadioButton) ─────────────────────────────
|
|
||||||
Use via Theme="{StaticResource NavTab}". Single GroupName = one active. -->
|
|
||||||
<ControlTheme x:Key="NavTab" TargetType="RadioButton">
|
<ControlTheme x:Key="NavTab" TargetType="RadioButton">
|
||||||
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||||
<Setter Property="FontWeight" Value="SemiBold" />
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
<Setter Property="FontSize" Value="14" />
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="MinWidth" Value="150" />
|
<Setter Property="MinWidth" Value="150" />
|
||||||
|
<Setter Property="MinHeight" Value="50" />
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
@@ -87,16 +79,98 @@
|
|||||||
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
||||||
</Style>
|
</Style>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
|
<ControlTheme x:Key="{x:Type TabItem}" TargetType="TabItem">
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="MinHeight" Value="24" />
|
||||||
|
<Setter Property="Padding" Value="12,3" />
|
||||||
|
<Setter Property="Margin" Value="0,0,2,0" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<Border Name="root" Background="Transparent"
|
||||||
|
BorderBrush="Transparent" BorderThickness="1,2,1,0"
|
||||||
|
CornerRadius="2,2,0,0"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter Name="PART_ContentPresenter"
|
||||||
|
Content="{TemplateBinding Header}"
|
||||||
|
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
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 CarbonHeaderBg}" />
|
||||||
|
<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>
|
</Styles.Resources>
|
||||||
|
|
||||||
<!-- ── Window / page surfaces ─────────────────────────────────────────── -->
|
|
||||||
<Style Selector="Window">
|
<Style Selector="Window">
|
||||||
<Setter Property="Background" Value="{StaticResource CarbonAppBg}" />
|
<Setter Property="Background" Value="{StaticResource CarbonAppBg}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
|
||||||
<Setter Property="FontFamily" Value="Tahoma, Segoe UI, Inter" />
|
<Setter Property="FontFamily" Value="Tahoma, Segoe UI, Inter" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="ComboBox">
|
||||||
|
<Setter Property="MinHeight" Value="24" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<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="Padding" Value="6,1" />
|
||||||
|
<Setter Property="CornerRadius" Value="2" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="NumericUpDown">
|
||||||
|
<Setter Property="MinHeight" Value="26" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="MinHeight" Value="25" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
</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" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Label">
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="Padding" Value="0,2" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── START button (full-width, bold) ────────────────────────────────── -->
|
|
||||||
<Style Selector="Button.Start">
|
<Style Selector="Button.Start">
|
||||||
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
||||||
<Setter Property="Foreground" Value="White" />
|
<Setter Property="Foreground" Value="White" />
|
||||||
@@ -118,7 +192,6 @@
|
|||||||
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── Accent button (Save etc.) ──────────────────────────────────────── -->
|
|
||||||
<Style Selector="Button.Accent /template/ ContentPresenter#PART_ContentPresenter">
|
<Style Selector="Button.Accent /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
||||||
<Setter Property="TextBlock.Foreground" Value="White" />
|
<Setter Property="TextBlock.Foreground" Value="White" />
|
||||||
@@ -128,7 +201,6 @@
|
|||||||
<Setter Property="TextBlock.Foreground" Value="White" />
|
<Setter Property="TextBlock.Foreground" Value="White" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── Flat button (toolbar/secondary) ────────────────────────────────── -->
|
|
||||||
<Style Selector="Button.Flat">
|
<Style Selector="Button.Flat">
|
||||||
<Setter Property="Background" Value="{StaticResource CarbonInputBg}" />
|
<Setter Property="Background" Value="{StaticResource CarbonInputBg}" />
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource CarbonBorderLight}" />
|
<Setter Property="BorderBrush" Value="{StaticResource CarbonBorderLight}" />
|
||||||
@@ -136,11 +208,10 @@
|
|||||||
<Setter Property="CornerRadius" Value="2" />
|
<Setter Property="CornerRadius" Value="2" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── Basic button (borderless icon buttons) ─────────────────────────── -->
|
|
||||||
<Style Selector="Button.Basic">
|
<Style Selector="Button.Basic">
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
<Setter Property="Padding" Value="6" />
|
<Setter Property="Padding" Value="4" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.Basic /template/ ContentPresenter#PART_ContentPresenter">
|
<Style Selector="Button.Basic /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
@@ -151,7 +222,34 @@
|
|||||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource CarbonFg}" />
|
<Setter Property="TextBlock.Foreground" Value="{StaticResource CarbonFg}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── Selection highlight in MaSzyna green (not the OS accent) ────────── -->
|
<Style Selector="StackPanel.Field">
|
||||||
|
<Setter Property="Spacing" Value="2" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.FieldLabel">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.FieldValue">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource CarbonAccentBright}" />
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="TextAlignment" Value="Right" />
|
||||||
|
<Setter Property="Margin" Value="8,0,0,0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.FieldHint">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
|
||||||
|
<Setter Property="FontSize" Value="11" />
|
||||||
|
<Setter Property="FontStyle" Value="Italic" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Separator">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CarbonBorderLight}" />
|
||||||
|
<Setter Property="Height" Value="1" />
|
||||||
|
<Setter Property="Margin" Value="0,2" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
<Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
||||||
</Style>
|
</Style>
|
||||||
@@ -171,36 +269,23 @@
|
|||||||
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── Tabs: smaller headers than Fluent's default, but tall enough that the
|
|
||||||
green selection pipe at the bottom doesn't touch the label. ─────────── -->
|
|
||||||
<Style Selector="TabItem">
|
|
||||||
<Setter Property="FontSize" Value="14" />
|
|
||||||
<Setter Property="MinHeight" Value="38" />
|
|
||||||
<Setter Property="Padding" Value="12,4,12,8" />
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- ── Compact list (tight rows that fill the available space) ─────────── -->
|
|
||||||
<!-- Transparent surface so the list blends into the GroupBox panel instead
|
|
||||||
of showing the Fluent theme's default light-grey ListBox background. -->
|
|
||||||
<Style Selector="ListBox.Compact">
|
<Style Selector="ListBox.Compact">
|
||||||
<Setter Property="Padding" Value="0" />
|
<Setter Property="Padding" Value="0" />
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="ListBox.Compact ListBoxItem">
|
<Style Selector="ListBox.Compact ListBoxItem">
|
||||||
<Setter Property="Padding" Value="6,2" />
|
<Setter Property="Padding" Value="4,0" />
|
||||||
<Setter Property="MinHeight" Value="0" />
|
<Setter Property="MinHeight" Value="17" />
|
||||||
<Setter Property="FontSize" Value="12" />
|
<Setter Property="FontSize" Value="13" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── Compact tree (tight scenery rows) ──────────────────────────────── -->
|
|
||||||
<Style Selector="TreeView.Compact TreeViewItem">
|
<Style Selector="TreeView.Compact TreeViewItem">
|
||||||
<Setter Property="MinHeight" Value="22" />
|
<Setter Property="MinHeight" Value="19" />
|
||||||
<Setter Property="FontSize" Value="12" />
|
<Setter Property="FontSize" Value="13" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TreeView.Compact TreeViewItem /template/ Border#PART_LayoutRoot">
|
<Style Selector="TreeView.Compact TreeViewItem /template/ Border#PART_LayoutRoot">
|
||||||
<Setter Property="MinHeight" Value="22" />
|
<Setter Property="MinHeight" Value="19" />
|
||||||
<Setter Property="Padding" Value="4,1" />
|
<Setter Property="Padding" Value="3,0" />
|
||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|||||||
143
StarterNG/Views/AboutWindow.cs
Normal file
143
StarterNG/Views/AboutWindow.cs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
namespace StarterNG.Views;
|
||||||
|
|
||||||
|
public static class AboutWindow
|
||||||
|
{
|
||||||
|
private const string RepositoryUrl = "https://github.com/maj00r/StarterNG";
|
||||||
|
|
||||||
|
private static readonly string[] ChangelogNames =
|
||||||
|
{
|
||||||
|
"changelog.txt", "starter/changelog.txt", "CHANGELOG.md"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Window Create()
|
||||||
|
{
|
||||||
|
var win = new Window
|
||||||
|
{
|
||||||
|
Title = App.Loc["AboutTitle"],
|
||||||
|
Width = 640,
|
||||||
|
Height = 460,
|
||||||
|
MinWidth = 480,
|
||||||
|
MinHeight = 320,
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||||
|
};
|
||||||
|
|
||||||
|
var header = new StackPanel { Spacing = 2, Margin = new Avalonia.Thickness(20, 18, 20, 12) };
|
||||||
|
header.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = "Starter MaSzyna",
|
||||||
|
FontSize = 22,
|
||||||
|
FontWeight = FontWeight.Bold
|
||||||
|
});
|
||||||
|
header.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = $"{App.Loc["Version"]} {AppInfo.Version}",
|
||||||
|
Opacity = 0.8
|
||||||
|
});
|
||||||
|
header.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = App.Loc["AboutForSimulator"],
|
||||||
|
Opacity = 0.8
|
||||||
|
});
|
||||||
|
|
||||||
|
var repo = new Button
|
||||||
|
{
|
||||||
|
Content = RepositoryUrl,
|
||||||
|
Tag = RepositoryUrl,
|
||||||
|
Padding = new Avalonia.Thickness(0),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
Margin = new Avalonia.Thickness(0, 6, 0, 10),
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
repo.Classes.Add("Basic");
|
||||||
|
repo.Click += (_, _) => OpenUrl(RepositoryUrl);
|
||||||
|
header.Children.Add(repo);
|
||||||
|
|
||||||
|
var changelog = new TextBox
|
||||||
|
{
|
||||||
|
Text = ReadChangelog(),
|
||||||
|
IsReadOnly = true,
|
||||||
|
AcceptsReturn = true,
|
||||||
|
TextWrapping = TextWrapping.NoWrap,
|
||||||
|
FontFamily = MonoFont.Family,
|
||||||
|
FontSize = 12,
|
||||||
|
Margin = new Avalonia.Thickness(20, 0, 20, 12)
|
||||||
|
};
|
||||||
|
ScrollViewer.SetHorizontalScrollBarVisibility(changelog, ScrollBarVisibility.Auto);
|
||||||
|
ScrollViewer.SetVerticalScrollBarVisibility(changelog, ScrollBarVisibility.Auto);
|
||||||
|
|
||||||
|
var close = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc["Close"],
|
||||||
|
MinWidth = 90,
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
close.Classes.Add("Accent");
|
||||||
|
close.Click += (_, _) => win.Close();
|
||||||
|
|
||||||
|
var footer = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Margin = new Avalonia.Thickness(20, 0, 20, 16),
|
||||||
|
Children = { close }
|
||||||
|
};
|
||||||
|
|
||||||
|
var root = new DockPanel();
|
||||||
|
DockPanel.SetDock(header, Dock.Top);
|
||||||
|
DockPanel.SetDock(footer, Dock.Bottom);
|
||||||
|
root.Children.Add(header);
|
||||||
|
root.Children.Add(footer);
|
||||||
|
|
||||||
|
var logLabel = new TextBlock
|
||||||
|
{
|
||||||
|
Text = App.Loc["AboutChangelog"],
|
||||||
|
Margin = new Avalonia.Thickness(20, 0, 20, 4),
|
||||||
|
Opacity = 0.8
|
||||||
|
};
|
||||||
|
DockPanel.SetDock(logLabel, Dock.Top);
|
||||||
|
root.Children.Add(logLabel);
|
||||||
|
root.Children.Add(changelog);
|
||||||
|
|
||||||
|
win.Content = root;
|
||||||
|
return win;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Infrastructure.Diagnostics.Log($"Open {url}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadChangelog()
|
||||||
|
{
|
||||||
|
foreach (string name in ChangelogNames)
|
||||||
|
{
|
||||||
|
string path = Path.Combine(Directory.GetCurrentDirectory(), name);
|
||||||
|
if (!File.Exists(path)) continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return File.ReadAllText(path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log($"Changelog {path}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return App.Loc["AboutNoChangelog"];
|
||||||
|
}
|
||||||
|
}
|
||||||
187
StarterNG/Views/AdvancedSettingsWindow.cs
Normal file
187
StarterNG/Views/AdvancedSettingsWindow.cs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using StarterNG.Infrastructure;
|
||||||
|
using SettingsModel = StarterNG.Classes.Settings;
|
||||||
|
|
||||||
|
namespace StarterNG.Views;
|
||||||
|
|
||||||
|
public sealed class AdvancedSettingsWindow : Window
|
||||||
|
{
|
||||||
|
private readonly TextBox _log = new()
|
||||||
|
{
|
||||||
|
IsReadOnly = true,
|
||||||
|
AcceptsReturn = true,
|
||||||
|
TextWrapping = TextWrapping.NoWrap,
|
||||||
|
FontFamily = MonoFont.Family,
|
||||||
|
FontSize = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly CheckBox _compressTex = new();
|
||||||
|
private readonly CheckBox _mipmaps = new();
|
||||||
|
private readonly CheckBox _scaleSpeculars = new();
|
||||||
|
private readonly CheckBox _shaderGamma = new();
|
||||||
|
private readonly CheckBox _useGles = new();
|
||||||
|
private readonly CheckBox _convertModels = new();
|
||||||
|
private readonly CheckBox _resourceMove = new();
|
||||||
|
private readonly CheckBox _resourceSweep = new();
|
||||||
|
private readonly CheckBox _ignoreIrrelevant = new();
|
||||||
|
private readonly Slider _threads = new() { Minimum = 0, Maximum = 16, TickFrequency = 1, IsSnapToTickEnabled = true };
|
||||||
|
private readonly TextBlock _threadsValue = new() { VerticalAlignment = VerticalAlignment.Center, MinWidth = 24 };
|
||||||
|
|
||||||
|
public event Action<string>? ToolRequested;
|
||||||
|
|
||||||
|
public AdvancedSettingsWindow()
|
||||||
|
{
|
||||||
|
Title = App.Loc["AdvancedSettings"];
|
||||||
|
Width = 620;
|
||||||
|
Height = 470;
|
||||||
|
MinWidth = 480;
|
||||||
|
MinHeight = 360;
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||||
|
|
||||||
|
_compressTex.Content = App.Loc["CompressTextures"];
|
||||||
|
_mipmaps.Content = App.Loc["ScreenMipmaps"];
|
||||||
|
_scaleSpeculars.Content = App.Loc["ScaleSpeculars"];
|
||||||
|
_shaderGamma.Content = App.Loc["ShaderGamma"];
|
||||||
|
_useGles.Content = App.Loc["UseGLES"];
|
||||||
|
_convertModels.Content = App.Loc["ExtendedModelConversion"];
|
||||||
|
_resourceMove.Content = App.Loc["GfxResourceMove"];
|
||||||
|
_resourceSweep.Content = App.Loc["GfxResourceSweep"];
|
||||||
|
_ignoreIrrelevant.Content = App.Loc["IgnoreIrrelevantTrains"];
|
||||||
|
_threads.PropertyChanged += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.Property == RangeBase.ValueProperty)
|
||||||
|
_threadsValue.Text = ((int)_threads.Value).ToString();
|
||||||
|
};
|
||||||
|
|
||||||
|
Load();
|
||||||
|
|
||||||
|
var tabs = new TabControl();
|
||||||
|
tabs.Items.Add(new TabItem { Header = App.Loc["AdvLogPage"], Content = BuildLogPage() });
|
||||||
|
tabs.Items.Add(new TabItem { Header = App.Loc["Advanced"], Content = BuildOptionsPage() });
|
||||||
|
tabs.Items.Add(new TabItem { Header = App.Loc["Tools"], Content = BuildToolsPage() });
|
||||||
|
|
||||||
|
var close = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc["Close"],
|
||||||
|
MinWidth = 100,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Margin = new Avalonia.Thickness(0, 8, 0, 0),
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
close.Classes.Add("Accent");
|
||||||
|
close.Click += (_, _) => { Store(); Close(); };
|
||||||
|
|
||||||
|
var root = new DockPanel { Margin = new Avalonia.Thickness(10) };
|
||||||
|
DockPanel.SetDock(close, Dock.Bottom);
|
||||||
|
root.Children.Add(close);
|
||||||
|
root.Children.Add(tabs);
|
||||||
|
Content = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control BuildLogPage()
|
||||||
|
{
|
||||||
|
_log.Text = Diagnostics.Text;
|
||||||
|
|
||||||
|
var clear = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc["AdvClearLog"],
|
||||||
|
Margin = new Avalonia.Thickness(0, 8, 0, 0),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
clear.Classes.Add("Flat");
|
||||||
|
clear.Click += (_, _) => { Diagnostics.Clear(); _log.Text = ""; };
|
||||||
|
|
||||||
|
ScrollViewer.SetHorizontalScrollBarVisibility(_log, ScrollBarVisibility.Auto);
|
||||||
|
ScrollViewer.SetVerticalScrollBarVisibility(_log, ScrollBarVisibility.Auto);
|
||||||
|
|
||||||
|
var panel = new DockPanel { Margin = new Avalonia.Thickness(8) };
|
||||||
|
DockPanel.SetDock(clear, Dock.Bottom);
|
||||||
|
panel.Children.Add(clear);
|
||||||
|
panel.Children.Add(_log);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control BuildOptionsPage()
|
||||||
|
{
|
||||||
|
var sp = new StackPanel { Margin = new Avalonia.Thickness(10), Spacing = 8 };
|
||||||
|
foreach (var c in new Control[]
|
||||||
|
{
|
||||||
|
_compressTex, _scaleSpeculars, _useGles, _shaderGamma, _mipmaps,
|
||||||
|
_convertModels, _resourceMove, _resourceSweep, _ignoreIrrelevant
|
||||||
|
})
|
||||||
|
sp.Children.Add(c);
|
||||||
|
|
||||||
|
var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Margin = new Avalonia.Thickness(0, 6, 0, 0) };
|
||||||
|
row.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = App.Loc["TrainPhysicsThreads"],
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
});
|
||||||
|
_threads.Width = 220;
|
||||||
|
row.Children.Add(_threads);
|
||||||
|
row.Children.Add(_threadsValue);
|
||||||
|
sp.Children.Add(row);
|
||||||
|
|
||||||
|
return new ScrollViewer { Content = sp, VerticalScrollBarVisibility = ScrollBarVisibility.Auto };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control BuildToolsPage()
|
||||||
|
{
|
||||||
|
var sp = new StackPanel { Margin = new Avalonia.Thickness(10), Spacing = 8 };
|
||||||
|
|
||||||
|
Button Tool(string key, string tag)
|
||||||
|
{
|
||||||
|
var b = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc[key],
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
b.Classes.Add("Flat");
|
||||||
|
b.Click += (_, _) => ToolRequested?.Invoke(tag);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
sp.Children.Add(Tool("AddAllCategory", "category"));
|
||||||
|
sp.Children.Add(Tool("AddUniqueMmd", "mmd"));
|
||||||
|
sp.Children.Add(Tool("RemoveAllTrains", "removeall"));
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Load()
|
||||||
|
{
|
||||||
|
var s = SettingsModel.Instance;
|
||||||
|
_compressTex.IsChecked = s.CompressTextures;
|
||||||
|
_mipmaps.IsChecked = s.ScreenMipmaps;
|
||||||
|
_scaleSpeculars.IsChecked = s.ScaleSpeculars;
|
||||||
|
_shaderGamma.IsChecked = s.ShaderGamma;
|
||||||
|
_useGles.IsChecked = s.UseGLES;
|
||||||
|
_convertModels.IsChecked = s.ExtendedModelConversion;
|
||||||
|
_resourceMove.IsChecked = s.GfxResourceMove;
|
||||||
|
_resourceSweep.IsChecked = s.GfxResourceSweep;
|
||||||
|
_ignoreIrrelevant.IsChecked = s.IgnoreIrrelevantTrains;
|
||||||
|
_threads.Value = s.TrainPhysicsThreads;
|
||||||
|
_threadsValue.Text = s.TrainPhysicsThreads.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Store()
|
||||||
|
{
|
||||||
|
var s = SettingsModel.Instance;
|
||||||
|
s.CompressTextures = _compressTex.IsChecked == true;
|
||||||
|
s.ScreenMipmaps = _mipmaps.IsChecked == true;
|
||||||
|
s.ScaleSpeculars = _scaleSpeculars.IsChecked == true;
|
||||||
|
s.ShaderGamma = _shaderGamma.IsChecked == true;
|
||||||
|
s.UseGLES = _useGles.IsChecked == true;
|
||||||
|
s.ExtendedModelConversion = _convertModels.IsChecked == true;
|
||||||
|
s.GfxResourceMove = _resourceMove.IsChecked == true;
|
||||||
|
s.GfxResourceSweep = _resourceSweep.IsChecked == true;
|
||||||
|
s.IgnoreIrrelevantTrains = _ignoreIrrelevant.IsChecked == true;
|
||||||
|
s.TrainPhysicsThreads = (int)_threads.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,11 +18,43 @@ public static class ConsistContextMenu
|
|||||||
Control owner,
|
Control owner,
|
||||||
Func<Trainset?> getTarget,
|
Func<Trainset?> getTarget,
|
||||||
Action<List<Dynamic>> applyVehicles,
|
Action<List<Dynamic>> applyVehicles,
|
||||||
Action? removeAll = null)
|
Action? removeAll = null,
|
||||||
|
Func<Dynamic?>? getVehicle = null,
|
||||||
|
Action<Dynamic>? removeVehicle = null,
|
||||||
|
Action? randomOrder = null,
|
||||||
|
Action? randomTurn = null)
|
||||||
{
|
{
|
||||||
var menu = new ContextMenu();
|
var menu = new ContextMenu();
|
||||||
|
|
||||||
menu.Opening += (_, _) => Populate(menu, owner, getTarget, applyVehicles, removeAll);
|
menu.Opening += (_, _) =>
|
||||||
|
{
|
||||||
|
Populate(menu, owner, getTarget, applyVehicles, removeAll);
|
||||||
|
|
||||||
|
if (randomOrder != null || randomTurn != null)
|
||||||
|
{
|
||||||
|
var schedule = new MenuItem { Header = App.Loc["ConsistSchedule"] };
|
||||||
|
if (randomOrder != null)
|
||||||
|
{
|
||||||
|
var mo = new MenuItem { Header = App.Loc["RandomizeOrder"] };
|
||||||
|
mo.Click += (_, _) => randomOrder();
|
||||||
|
schedule.Items.Add(mo);
|
||||||
|
}
|
||||||
|
if (randomTurn != null)
|
||||||
|
{
|
||||||
|
var mt = new MenuItem { Header = App.Loc["RandomizeOrientation"] };
|
||||||
|
mt.Click += (_, _) => randomTurn();
|
||||||
|
schedule.Items.Add(mt);
|
||||||
|
}
|
||||||
|
menu.Items.Add(schedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeVehicle == null || getVehicle?.Invoke() is not { } v) return;
|
||||||
|
|
||||||
|
menu.Items.Add(new Separator());
|
||||||
|
var mi = new MenuItem { Header = App.Loc["RemoveVehicle"] };
|
||||||
|
mi.Click += (_, _) => removeVehicle(v);
|
||||||
|
menu.Items.Add(mi);
|
||||||
|
};
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +82,31 @@ public static class ConsistContextMenu
|
|||||||
}
|
}
|
||||||
menu.Items.Add(load);
|
menu.Items.Add(load);
|
||||||
|
|
||||||
|
var append = new MenuItem { Header = App.Loc["WarehouseAppend"] };
|
||||||
|
if (presets.Count == 0)
|
||||||
|
{
|
||||||
|
append.Items.Add(new MenuItem { Header = App.Loc["PresetEmpty"], IsEnabled = false });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var preset in presets)
|
||||||
|
{
|
||||||
|
var entry = preset;
|
||||||
|
var mi = new MenuItem { Header = entry.Name };
|
||||||
|
mi.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
var added = ConsistText.VehiclesFrom(entry.Entry);
|
||||||
|
if (added is null || added.Count == 0) return;
|
||||||
|
var merged = target is null
|
||||||
|
? added
|
||||||
|
: new List<Dynamic>(target.Vehicles).Concat(added).ToList();
|
||||||
|
applyVehicles(merged);
|
||||||
|
};
|
||||||
|
append.Items.Add(mi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menu.Items.Add(append);
|
||||||
|
|
||||||
var sort = new MenuItem { Header = App.Loc["PresetSort"] };
|
var sort = new MenuItem { Header = App.Loc["PresetSort"] };
|
||||||
var sortName = new MenuItem
|
var sortName = new MenuItem
|
||||||
{
|
{
|
||||||
@@ -148,7 +205,7 @@ public static class ConsistContextMenu
|
|||||||
|
|
||||||
var nameBox = new TextBox
|
var nameBox = new TextBox
|
||||||
{
|
{
|
||||||
Watermark = App.Loc["PresetNamePrompt"],
|
PlaceholderText = App.Loc["PresetNamePrompt"],
|
||||||
Text = DefaultName(target),
|
Text = DefaultName(target),
|
||||||
MinWidth = 220
|
MinWidth = 220
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,162 +7,177 @@
|
|||||||
x:CompileBindings="True"
|
x:CompileBindings="True"
|
||||||
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
|
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
|
||||||
x:Class="StarterNG.Views.Depot">
|
x:Class="StarterNG.Views.Depot">
|
||||||
<Grid Margin="0" ColumnSpacing="8" RowSpacing="8">
|
<Grid Margin="0" ColumnSpacing="3" RowSpacing="3">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="340" />
|
<ColumnDefinition Width="270" MinWidth="220" />
|
||||||
<ColumnDefinition />
|
<ColumnDefinition />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<!-- Consists list and the brakes/loads editor share the upper area,
|
<RowDefinition Height="*" MinHeight="230" />
|
||||||
the editor taking a bit more of it. -->
|
<RowDefinition Height="275" MinHeight="260" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="175" MinHeight="165" />
|
||||||
<RowDefinition Height="1.25*" MinHeight="250" />
|
|
||||||
<RowDefinition Height="190" />
|
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Vehicle database browser (left, rows 0-1) -->
|
<Border Grid.Column="0" Grid.Row="0" Background="{StaticResource CarbonPanelBg}"
|
||||||
<Border Grid.RowSpan="2" Background="{StaticResource CarbonPanelBg}"
|
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||||
CornerRadius="2" Padding="8">
|
CornerRadius="2" Padding="8">
|
||||||
<DockPanel LastChildFill="True">
|
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto">
|
||||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6" LastChildFill="True">
|
<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="11"
|
||||||
VerticalAlignment="Center" Margin="8,0,0,0"
|
VerticalAlignment="Center" Margin="8,0,0,0"
|
||||||
Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}"
|
Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}"
|
||||||
IsCheckedChanged="HideArchivalCheck_OnChanged" />
|
IsCheckedChanged="HideArchivalCheck_OnChanged" />
|
||||||
<TextBox Name="searchBox" VerticalAlignment="Center"
|
<TextBox Name="searchBox" VerticalAlignment="Center"
|
||||||
Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
PlaceholderText="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
||||||
TextChanged="SearchBox_OnTextChanged" />
|
TextChanged="SearchBox_OnTextChanged" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<Grid DockPanel.Dock="Top" RowDefinitions="Auto,Auto" RowSpacing="6" Margin="0,0,0,6">
|
<Grid Grid.Row="1" RowDefinitions="Auto,Auto" RowSpacing="6" Margin="0,0,0,6">
|
||||||
<ComboBox Name="categoryCombo" Grid.Row="0" HorizontalAlignment="Stretch"
|
<DockPanel Grid.Row="0" LastChildFill="True">
|
||||||
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
<Button Name="textureBaseButton" DockPanel.Dock="Right" Classes="Flat"
|
||||||
PointerWheelChanged="Combo_OnPointerWheel"
|
Width="30" Margin="4,0,0,0" Cursor="Hand" Content="🔍"
|
||||||
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
HorizontalContentAlignment="Center"
|
||||||
|
Click="TextureBaseButton_OnClick"
|
||||||
|
ToolTip.Tip="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox Name="categoryCombo" HorizontalAlignment="Stretch"
|
||||||
|
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
||||||
|
PointerWheelChanged="Combo_OnPointerWheel"
|
||||||
|
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
||||||
|
</DockPanel>
|
||||||
<ComboBox Name="classCombo" Grid.Row="1" HorizontalAlignment="Stretch"
|
<ComboBox Name="classCombo" Grid.Row="1" HorizontalAlignment="Stretch"
|
||||||
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
|
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
|
||||||
PointerWheelChanged="Combo_OnPointerWheel"
|
PointerWheelChanged="Combo_OnPointerWheel"
|
||||||
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- The arrow stays enabled with nothing selected - adding a whole
|
<Border Name="miniPreviewPanel" Grid.Row="3" Height="66"
|
||||||
category needs no highlighted entry. -->
|
Margin="0,6,0,0"
|
||||||
<DockPanel DockPanel.Dock="Bottom" Margin="0,8,0,0">
|
Background="{StaticResource CarbonInputBg}"
|
||||||
<Button Name="addMoreButton" DockPanel.Dock="Right" Classes="Flat Accent"
|
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||||
Width="28" Margin="4,0,0,0" Cursor="Hand" Content="▾"
|
CornerRadius="2" Cursor="Hand"
|
||||||
HorizontalContentAlignment="Center"
|
PointerPressed="MiniPreview_OnPointerPressed">
|
||||||
ToolTip.Tip="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}">
|
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
||||||
<Button.Flyout>
|
</Border>
|
||||||
<MenuFlyout Placement="TopEdgeAlignedRight">
|
|
||||||
<MenuItem Click="AddAllCategoryButton_OnClick"
|
|
||||||
Header="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
ToolTip.Tip="{Binding [TipAddAllCategory], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<MenuItem Click="AddUniqueMmdButton_OnClick"
|
|
||||||
Header="{Binding [AddUniqueMmd], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
ToolTip.Tip="{Binding [TipAddUniqueMmd], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Click="TextureBaseButton_OnClick"
|
|
||||||
Header="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</MenuFlyout>
|
|
||||||
</Button.Flyout>
|
|
||||||
</Button>
|
|
||||||
<Button Name="addVehicleButton" Classes="Flat Accent"
|
|
||||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
|
||||||
IsEnabled="False" Cursor="Hand"
|
|
||||||
Click="AddVehicleButton_OnClick"
|
|
||||||
Content="{Binding [AddVehicle], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</DockPanel>
|
|
||||||
<Grid Name="browserDetails" DockPanel.Dock="Bottom" ColumnDefinitions="130,*"
|
|
||||||
ColumnSpacing="6" Margin="0,6,0,0" IsVisible="False">
|
|
||||||
<Border Name="miniPreviewPanel" Grid.Column="0" Height="66"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Background="{StaticResource CarbonInputBg}"
|
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
|
||||||
CornerRadius="2" Cursor="Hand"
|
|
||||||
PointerPressed="MiniPreview_OnPointerPressed">
|
|
||||||
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
|
||||||
</Border>
|
|
||||||
<StackPanel Name="textureInfoPanel" Grid.Column="1" Spacing="1"
|
|
||||||
VerticalAlignment="Center" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Flat list of vehicle entries (plain text, no expanders).
|
<Button Name="addVehicleButton" Grid.Row="4" Classes="Flat Accent"
|
||||||
Selecting one shows its mini and enables Add; double-click
|
Margin="0,8,0,0" Cursor="Hand" IsEnabled="False"
|
||||||
replaces the selected consist vehicle. -->
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
<ListBox Name="vehicleListBox" Classes="Compact" MinHeight="120"
|
Click="AddVehicleButton_OnClick"
|
||||||
|
Content="{Binding [AddVehicle], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
|
||||||
|
<ListBox Name="vehicleListBox" Grid.Row="2" Classes="Compact"
|
||||||
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
||||||
DoubleTapped="VehicleListBox_OnDoubleTapped"
|
DoubleTapped="VehicleListBox_OnDoubleTapped"
|
||||||
PointerPressed="VehicleListBox_OnPointerPressed" />
|
PointerPressed="VehicleListBox_OnPointerPressed" />
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Consists present on the scenery selected in the Scenarios tab.
|
|
||||||
Selecting one loads it into the editor below; the header carries the
|
|
||||||
scenery name (see PopulateSceneryConsists). -->
|
|
||||||
<HeaderedContentControl Name="mapConsistsPanel" Grid.Column="1" Grid.Row="0"
|
<HeaderedContentControl Name="mapConsistsPanel" Grid.Column="1" Grid.Row="0"
|
||||||
Theme="{StaticResource GroupBox}">
|
Theme="{StaticResource GroupBox}">
|
||||||
<DockPanel LastChildFill="True">
|
<TabControl>
|
||||||
<ListBox Name="sceneryConsistList" Classes="Compact"
|
<TabItem Header="{Binding [SceneryConsists], Source={x:Static starterNg:App.Loc}}">
|
||||||
SelectionChanged="SceneryConsistList_OnSelectionChanged"
|
<DockPanel LastChildFill="True">
|
||||||
KeyDown="SceneryConsistList_OnKeyDown" />
|
<Button Name="addToWarehouseButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||||
</DockPanel>
|
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Click="AddToWarehouseButton_OnClick"
|
||||||
|
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"
|
||||||
|
IsCheckedChanged="ConsistFilter_OnChanged"
|
||||||
|
Content="{Binding [ShowDrivableOnly], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox Name="depotShowAiCheck" FontSize="11"
|
||||||
|
IsCheckedChanged="ConsistFilter_OnChanged"
|
||||||
|
Content="{Binding [ShowAiVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</StackPanel>
|
||||||
|
<ListBox Name="sceneryConsistList" Classes="Compact"
|
||||||
|
SelectionChanged="SceneryConsistList_OnSelectionChanged"
|
||||||
|
KeyDown="SceneryConsistList_OnKeyDown" />
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="{Binding [WarehouseConsists], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<DockPanel LastChildFill="True">
|
||||||
|
<Button Name="removeFromWarehouseButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||||
|
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Click="RemoveFromWarehouseButton_OnClick"
|
||||||
|
Content="{Binding [RemoveFromWarehouse], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ListBox Name="warehouseList" Classes="Compact"
|
||||||
|
DoubleTapped="WarehouseList_OnDoubleTapped" />
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
</HeaderedContentControl>
|
</HeaderedContentControl>
|
||||||
|
|
||||||
<!-- Brakes / Loads editor (middle-right). Header carries the vehicle
|
<HeaderedContentControl Name="selectedVehiclePanel" Grid.Row="1" Grid.ColumnSpan="2"
|
||||||
name (see UpdateDetails). -->
|
|
||||||
<HeaderedContentControl Name="selectedVehiclePanel" Grid.Column="1" Grid.Row="1"
|
|
||||||
Theme="{StaticResource GroupBox}">
|
Theme="{StaticResource GroupBox}">
|
||||||
<DockPanel LastChildFill="True">
|
<Grid ColumnDefinitions="290*,3,380*,3,280*">
|
||||||
<TabControl>
|
<TabControl Grid.Column="0">
|
||||||
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
<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">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel Name="brakesPanel" Margin="4" Spacing="8" />
|
<StackPanel Name="consistTexturePanel" Margin="4" Spacing="6" />
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="{Binding [Loads], Source={x:Static starterNg:App.Loc}}">
|
</TabControl>
|
||||||
|
|
||||||
|
<TabControl Grid.Column="2">
|
||||||
|
<TabItem Header="{Binding [Configuration], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel Name="loadsPanel" Margin="4" Spacing="8" />
|
<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" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
|
||||||
|
<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>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="{Binding [Damage], Source={x:Static starterNg:App.Loc}}">
|
<TabItem Header="{Binding [Damage], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel Name="damagePanel" Margin="4" Spacing="8" />
|
<StackPanel Name="damagePanel" Margin="4" Spacing="6" />
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</HeaderedContentControl>
|
</HeaderedContentControl>
|
||||||
|
|
||||||
<!-- Consist (full-width bottom, horizontally scrollable) -->
|
|
||||||
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
|
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
|
||||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<!-- Train totals + randomize tools -->
|
|
||||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
||||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Spacing="4">
|
<StackPanel Name="trainStats" Orientation="Horizontal"
|
||||||
<Button Name="randomTexturesButton" Classes="Basic" FontSize="11"
|
VerticalAlignment="Center" />
|
||||||
Padding="8,2" Cursor="Hand" Click="RandomTexturesButton_OnClick"
|
|
||||||
Content="{Binding [RandomizeTextures], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
ToolTip.Tip="{Binding [TipRandomizeTextures], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Button Name="randomOrderButton" Classes="Basic" FontSize="11"
|
|
||||||
Padding="8,2" Cursor="Hand" Click="RandomOrderButton_OnClick"
|
|
||||||
Content="{Binding [RandomizeOrder], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
ToolTip.Tip="{Binding [TipRandomizeOrder], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Button Name="randomOrientButton" Classes="Basic" FontSize="11"
|
|
||||||
Padding="8,2" Cursor="Hand" Click="RandomOrientButton_OnClick"
|
|
||||||
Content="{Binding [RandomizeOrientation], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
ToolTip.Tip="{Binding [TipRandomizeOrientation], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</StackPanel>
|
|
||||||
<TextBlock Name="trainStats" FontSize="12" Opacity="0.85"
|
|
||||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<Panel Name="consistDropTarget">
|
<Panel Name="consistDropTarget">
|
||||||
<TextBlock Name="emptyHint" VerticalAlignment="Center" HorizontalAlignment="Center"
|
<TextBlock Name="emptyHint" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
TextWrapping="Wrap" MaxWidth="520" Opacity="0.6" TextAlignment="Center"
|
TextWrapping="Wrap" MaxWidth="520" Opacity="0.6" TextAlignment="Center"
|
||||||
Text="{Binding [ConsistEmpty], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding [ConsistEmpty], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
<ScrollViewer Name="consistScroll"
|
||||||
<StackPanel Name="consistStack" Orientation="Horizontal"
|
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||||
VerticalAlignment="Center" Spacing="0" />
|
<Panel>
|
||||||
|
<StackPanel Name="consistStack" Orientation="Horizontal"
|
||||||
|
VerticalAlignment="Center" Spacing="0" />
|
||||||
|
<Canvas Name="consistOverlay" IsHitTestVisible="False" />
|
||||||
|
</Panel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Panel>
|
</Panel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
262
StarterNG/Views/RulesWindow.cs
Normal file
262
StarterNG/Views/RulesWindow.cs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using StarterNG.Classes;
|
||||||
|
using StarterNG.Domain;
|
||||||
|
using StarterNG.Infrastructure;
|
||||||
|
|
||||||
|
namespace StarterNG.Views;
|
||||||
|
|
||||||
|
public sealed class RulesWindow : Window
|
||||||
|
{
|
||||||
|
private readonly List<List<string>> _rules = new();
|
||||||
|
private readonly ListBox _ruleList = new() { Classes = { "Compact" } };
|
||||||
|
private readonly WrapPanel _members = new() { Orientation = Orientation.Horizontal };
|
||||||
|
private readonly ComboBox _category = new();
|
||||||
|
private readonly ComboBox _model = new();
|
||||||
|
private readonly VehicleDatabase _db = GameData.Instance.Vehicles;
|
||||||
|
|
||||||
|
public RulesWindow()
|
||||||
|
{
|
||||||
|
Title = App.Loc["RandomizeTexturesRules"];
|
||||||
|
Width = 720;
|
||||||
|
Height = 460;
|
||||||
|
MinWidth = 560;
|
||||||
|
MinHeight = 360;
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||||
|
|
||||||
|
LoadRules();
|
||||||
|
BuildCategories();
|
||||||
|
|
||||||
|
_ruleList.SelectionChanged += (_, _) => ShowMembers();
|
||||||
|
|
||||||
|
var add = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc["RuleAdd"],
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
add.Classes.Add("Flat");
|
||||||
|
add.Click += (_, _) => AddToRule();
|
||||||
|
|
||||||
|
_category.SelectionChanged += (_, _) => FillModels();
|
||||||
|
|
||||||
|
var addRow = new Grid
|
||||||
|
{
|
||||||
|
Margin = new Avalonia.Thickness(0, 8, 0, 0),
|
||||||
|
ColumnDefinitions = new ColumnDefinitions("*,6,*,6,Auto")
|
||||||
|
};
|
||||||
|
_category.MinWidth = 0;
|
||||||
|
_model.MinWidth = 0;
|
||||||
|
_category.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||||
|
_model.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||||
|
Grid.SetColumn(_category, 0);
|
||||||
|
Grid.SetColumn(_model, 2);
|
||||||
|
Grid.SetColumn(add, 4);
|
||||||
|
addRow.Children.Add(_category);
|
||||||
|
addRow.Children.Add(_model);
|
||||||
|
addRow.Children.Add(add);
|
||||||
|
|
||||||
|
var save = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc["RandomizeTexturesRulesSave"],
|
||||||
|
MinWidth = 110,
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
save.Classes.Add("Accent");
|
||||||
|
save.Click += async (_, _) => { if (await SaveRules()) Close(); };
|
||||||
|
|
||||||
|
var cancel = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc["Cancel"],
|
||||||
|
MinWidth = 90,
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
cancel.Classes.Add("Flat");
|
||||||
|
cancel.Click += (_, _) => Close();
|
||||||
|
|
||||||
|
var footer = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Spacing = 8,
|
||||||
|
Margin = new Avalonia.Thickness(0, 10, 0, 0),
|
||||||
|
Children = { cancel, save }
|
||||||
|
};
|
||||||
|
|
||||||
|
var right = new DockPanel { LastChildFill = true };
|
||||||
|
DockPanel.SetDock(addRow, Dock.Bottom);
|
||||||
|
DockPanel.SetDock(footer, Dock.Bottom);
|
||||||
|
right.Children.Add(footer);
|
||||||
|
right.Children.Add(addRow);
|
||||||
|
right.Children.Add(new ScrollViewer
|
||||||
|
{
|
||||||
|
Content = _members,
|
||||||
|
VerticalScrollBarVisibility = ScrollBarVisibility.Auto
|
||||||
|
});
|
||||||
|
|
||||||
|
var grid = new Grid
|
||||||
|
{
|
||||||
|
Margin = new Avalonia.Thickness(10),
|
||||||
|
ColumnDefinitions = new ColumnDefinitions("260,10,*")
|
||||||
|
};
|
||||||
|
Grid.SetColumn(_ruleList, 0);
|
||||||
|
Grid.SetColumn(right, 2);
|
||||||
|
grid.Children.Add(_ruleList);
|
||||||
|
grid.Children.Add(right);
|
||||||
|
|
||||||
|
Content = grid;
|
||||||
|
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadRules()
|
||||||
|
{
|
||||||
|
string path = TexRandomizer.RulesPath;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
foreach (string line in File.ReadAllLines(path))
|
||||||
|
{
|
||||||
|
var members = line.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.Where(x => x.Length > 0)
|
||||||
|
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
if (members.Count > 0)
|
||||||
|
_rules.Add(members);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Diagnostics.Log($"reguly.txt {path}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_rules.Add(new List<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async System.Threading.Tasks.Task<bool> SaveRules()
|
||||||
|
{
|
||||||
|
string path = TexRandomizer.RulesPath;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? dir = Path.GetDirectoryName(path);
|
||||||
|
if (!string.IsNullOrEmpty(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
File.WriteAllLines(path,
|
||||||
|
_rules.Where(r => r.Count > 0).Select(r => string.Join(",", r)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await Diagnostics.ReportAsync(path, ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshList()
|
||||||
|
{
|
||||||
|
int keep = _ruleList.SelectedIndex;
|
||||||
|
_ruleList.Items.Clear();
|
||||||
|
|
||||||
|
foreach (var rule in _rules)
|
||||||
|
_ruleList.Items.Add(new ListBoxItem
|
||||||
|
{
|
||||||
|
Content = new TextBlock
|
||||||
|
{
|
||||||
|
Text = rule.Count == 0 ? App.Loc["RuleNew"] : string.Join(" ", rule),
|
||||||
|
TextTrimming = TextTrimming.CharacterEllipsis,
|
||||||
|
Opacity = rule.Count == 0 ? 0.6 : 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_ruleList.SelectedIndex = keep >= 0 && keep < _rules.Count ? keep : 0;
|
||||||
|
ShowMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowMembers()
|
||||||
|
{
|
||||||
|
_members.Children.Clear();
|
||||||
|
int idx = _ruleList.SelectedIndex;
|
||||||
|
if (idx < 0 || idx >= _rules.Count) return;
|
||||||
|
|
||||||
|
foreach (string member in _rules[idx])
|
||||||
|
{
|
||||||
|
string name = member;
|
||||||
|
var btn = new Button
|
||||||
|
{
|
||||||
|
Content = name + " ✕",
|
||||||
|
FontSize = 12,
|
||||||
|
Margin = new Avalonia.Thickness(0, 0, 4, 4),
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
btn.Classes.Add("Flat");
|
||||||
|
ToolTip.SetTip(btn, App.Loc["RuleRemove"]);
|
||||||
|
btn.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
_rules[idx].Remove(name);
|
||||||
|
RefreshList();
|
||||||
|
};
|
||||||
|
_members.Children.Add(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildCategories()
|
||||||
|
{
|
||||||
|
var cats = _db.Textures
|
||||||
|
.Select(t => t.ResolvedCategory)
|
||||||
|
.Where(c => !string.IsNullOrEmpty(c))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(c => c, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (string? c in cats)
|
||||||
|
_category.Items.Add(new ComboBoxItem { Content = c, Tag = c });
|
||||||
|
|
||||||
|
if (_category.Items.Count > 0)
|
||||||
|
_category.SelectedIndex = 0;
|
||||||
|
FillModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillModels()
|
||||||
|
{
|
||||||
|
_model.Items.Clear();
|
||||||
|
string? cat = (_category.SelectedItem as ComboBoxItem)?.Tag as string;
|
||||||
|
|
||||||
|
var models = _db.Textures
|
||||||
|
.Where(t => cat == null ||
|
||||||
|
string.Equals(t.ResolvedCategory, cat, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(t => t.Model ?? "")
|
||||||
|
.Where(m => m.Length > 0)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(m => m, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (string m in models)
|
||||||
|
_model.Items.Add(m);
|
||||||
|
|
||||||
|
if (_model.Items.Count > 0)
|
||||||
|
_model.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToRule()
|
||||||
|
{
|
||||||
|
int idx = _ruleList.SelectedIndex;
|
||||||
|
if (idx < 0 || idx >= _rules.Count) return;
|
||||||
|
if (_model.SelectedItem is not string model || model.Length == 0) return;
|
||||||
|
|
||||||
|
if (_rules[idx].Contains(model, StringComparer.OrdinalIgnoreCase))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_rules[idx].Add(model);
|
||||||
|
_rules[idx].Sort(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (idx == _rules.Count - 1)
|
||||||
|
_rules.Add(new List<string>());
|
||||||
|
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,103 +7,104 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="StarterNG.Views.Scenarios"
|
x:Class="StarterNG.Views.Scenarios"
|
||||||
x:CompileBindings="True">
|
x:CompileBindings="True">
|
||||||
<Grid Margin="0" ColumnSpacing="8" RowSpacing="8">
|
<Grid Margin="0" ColumnSpacing="0" RowSpacing="3">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="280" />
|
<ColumnDefinition Width="*" MinWidth="200" MaxWidth="460" />
|
||||||
<ColumnDefinition />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="280" />
|
<ColumnDefinition Width="3.2*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
<RowDefinition Height="150" />
|
<RowDefinition Height="150" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Scenery tree (left) -->
|
|
||||||
<HeaderedContentControl Grid.Column="0" Grid.Row="0" Theme="{StaticResource GroupBox}"
|
<HeaderedContentControl Grid.Column="0" Grid.Row="0" Theme="{StaticResource GroupBox}"
|
||||||
Classes="Compact"
|
Classes="Compact"
|
||||||
Header="{Binding [Sceneries], Source={x:Static starterNg:App.Loc}}">
|
Header="{Binding [Sceneries], Source={x:Static starterNg:App.Loc}}">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<ToggleSwitch DockPanel.Dock="Bottom" Name="archivalSwitch"
|
<CheckBox DockPanel.Dock="Bottom" Name="archivalSwitch"
|
||||||
Margin="2,4,0,0" FontSize="11"
|
Margin="2,4,0,0" FontSize="11"
|
||||||
IsCheckedChanged="ArchivalSwitch_OnChanged"
|
IsCheckedChanged="ArchivalSwitch_OnChanged"
|
||||||
OnContent="{Binding [ShowArchival], Source={x:Static starterNg:App.Loc}}"
|
Content="{Binding [HideArchivalSceneries], Source={x:Static starterNg:App.Loc}}" />
|
||||||
OffContent="{Binding [ShowArchival], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8" Margin="2,4,0,0">
|
|
||||||
<Button x:Name="reloadButton" Classes="Flat" FontSize="11" Padding="8,2" Cursor="Hand"
|
|
||||||
Click="ReloadSceneries_OnClick"
|
|
||||||
Content="{Binding [ReloadSceneries], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Button Classes="Flat" FontSize="11" Padding="8,2" Cursor="Hand"
|
|
||||||
Click="OpenSceneryDir_OnClick"
|
|
||||||
Content="{Binding [OpenSceneryFolder], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</StackPanel>
|
|
||||||
<TreeView Name="sceneryList" Classes="Compact"
|
<TreeView Name="sceneryList" Classes="Compact"
|
||||||
SelectionChanged="SceneryList_OnSelectionChanged" />
|
SelectionChanged="SceneryList_OnSelectionChanged">
|
||||||
|
<TreeView.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Click="ReloadSceneries_OnClick"
|
||||||
|
Header="{Binding [ReloadSceneries], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<MenuItem Click="OpenSceneryDir_OnClick"
|
||||||
|
Header="{Binding [OpenSceneryFolder], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ContextMenu>
|
||||||
|
</TreeView.ContextMenu>
|
||||||
|
</TreeView>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</HeaderedContentControl>
|
</HeaderedContentControl>
|
||||||
|
|
||||||
<!-- Centre: trainsets list (compact, fills) + description/weather/timetable tabs -->
|
<GridSplitter Grid.Column="1" Grid.Row="0" Width="6"
|
||||||
<Grid Grid.Column="1" Grid.Row="0" RowSpacing="8">
|
Background="Transparent" ResizeDirection="Columns" />
|
||||||
|
|
||||||
|
<Grid Grid.Column="2" Grid.Row="0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="2*" />
|
<RowDefinition Height="2*" MinHeight="90" />
|
||||||
<RowDefinition Height="3*" />
|
<RowDefinition Height="4" />
|
||||||
|
<RowDefinition Height="3*" MinHeight="140" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<HeaderedContentControl Grid.Row="0" Theme="{StaticResource GroupBox}"
|
<HeaderedContentControl Grid.Row="0" Theme="{StaticResource GroupBox}"
|
||||||
Header="{Binding [Vehicles], Source={x:Static starterNg:App.Loc}}">
|
Header="{Binding [SceneryConsists], Source={x:Static starterNg:App.Loc}}">
|
||||||
<DockPanel LastChildFill="True">
|
<HeaderedContentControl.Tag>
|
||||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="16"
|
<Button Name="randomTexturesButton" Classes="Flat" Cursor="Hand"
|
||||||
Margin="2,4,0,0">
|
FontSize="11" MinHeight="0" Padding="10,1"
|
||||||
<CheckBox Name="showAiCheck" FontSize="11"
|
Click="RandomTexturesButton_OnClick"
|
||||||
IsCheckedChanged="VehicleFilter_OnChanged"
|
Content="{Binding [RandomizeTextures], Source={x:Static starterNg:App.Loc}}"
|
||||||
Content="{Binding [ShowAiVehicles], Source={x:Static starterNg:App.Loc}}" />
|
ToolTip.Tip="{Binding [TipRandomizeTextures], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<CheckBox Name="drivableOnlyCheck" FontSize="11"
|
</HeaderedContentControl.Tag>
|
||||||
IsCheckedChanged="VehicleFilter_OnChanged"
|
|
||||||
Content="{Binding [ShowDrivableOnly], Source={x:Static starterNg:App.Loc}}" />
|
<ListBox Name="vehicleList" Classes="Compact"
|
||||||
</StackPanel>
|
SelectionChanged="VehicleList_OnSelectionChanged" />
|
||||||
<ListBox Name="vehicleList" Classes="Compact"
|
|
||||||
SelectionChanged="VehicleList_OnSelectionChanged" />
|
|
||||||
</DockPanel>
|
|
||||||
</HeaderedContentControl>
|
</HeaderedContentControl>
|
||||||
|
|
||||||
<TabControl Grid.Row="1">
|
<GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch"
|
||||||
|
Background="Transparent" ResizeDirection="Rows" />
|
||||||
|
|
||||||
|
<TabControl Grid.Row="2">
|
||||||
<TabItem Header="{Binding [TabDescription], Source={x:Static starterNg:App.Loc}}">
|
<TabItem Header="{Binding [TabDescription], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<Grid RowDefinitions="2*,6,*" Margin="4">
|
||||||
<TextBlock TextWrapping="Wrap" Name="missionDescription" Margin="4" />
|
<Grid Grid.Row="0" ColumnDefinitions="*,3,225">
|
||||||
</ScrollViewer>
|
<HeaderedContentControl Grid.Column="0" Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [SceneryDescription], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBlock TextWrapping="Wrap" Name="sceneryDescription" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
|
||||||
|
<Border Grid.Column="2" ClipToBounds="True"
|
||||||
|
Background="{StaticResource CarbonPanelBg}"
|
||||||
|
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1">
|
||||||
|
<Image Name="sceneryImage" Stretch="Uniform" Margin="2" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBlock TextWrapping="Wrap" Name="missionDescription" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="{Binding [Weather], Source={x:Static starterNg:App.Loc}}">
|
<TabItem Header="{Binding [Weather], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<StackPanel Name="weatherForm" Margin="8,6" Spacing="8" IsEnabled="False">
|
||||||
<StackPanel Name="weatherForm" Margin="8" Spacing="8" IsEnabled="False">
|
<Grid ColumnDefinitions="Auto,12,380,8,70"
|
||||||
<Grid ColumnDefinitions="130,*" RowSpacing="8">
|
RowDefinitions="Auto,6,Auto,14,Auto,Auto,8,Auto,Auto,14,Auto,14,Auto">
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
|
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
|
||||||
Text="{Binding [Time], Source={x:Static starterNg:App.Loc}}" />
|
Foreground="{StaticResource CarbonFgDim}"
|
||||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
Text="{Binding [Date], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<TimePicker Name="weatherTime" ClockIdentifier="24HourClock"
|
<Grid Grid.Row="0" Grid.Column="2" ColumnDefinitions="150,10,150,10,*">
|
||||||
MinuteIncrement="1" VerticalAlignment="Center"
|
<DatePicker Grid.Column="0" Name="weatherDate"
|
||||||
SelectedTimeChanged="WeatherTime_OnChanged" />
|
YearVisible="False" MinWidth="0"
|
||||||
<Button Name="weatherTimeNow" Classes="Flat" Cursor="Hand"
|
VerticalAlignment="Center" HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Center" Padding="10,4"
|
SelectedDateChanged="WeatherDate_OnChanged" />
|
||||||
Click="WeatherTimeNow_OnClick"
|
<ComboBox Grid.Column="2" Name="weatherSeason"
|
||||||
Content="{Binding [CurrentTime], Source={x:Static starterNg:App.Loc}}" />
|
HorizontalAlignment="Stretch" VerticalAlignment="Center"
|
||||||
<Button Name="weatherRestore" Classes="Flat" Cursor="Hand"
|
|
||||||
VerticalAlignment="Center" Padding="10,4"
|
|
||||||
Click="WeatherRestore_OnClick"
|
|
||||||
Content="{Binding [RestoreWeather], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
|
|
||||||
Text="{Binding [Season], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox Grid.Row="1" Grid.Column="1" Name="weatherSeason" Width="180"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
SelectionChanged="WeatherSeason_OnChanged">
|
SelectionChanged="WeatherSeason_OnChanged">
|
||||||
<ComboBoxItem Tag="0" Content="{Binding [SeasonNow], Source={x:Static starterNg:App.Loc}}" />
|
<ComboBoxItem Tag="0" Content="{Binding [SeasonNow], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<ComboBoxItem Tag="113" Content="{Binding [SeasonSpring], Source={x:Static starterNg:App.Loc}}" />
|
<ComboBoxItem Tag="113" Content="{Binding [SeasonSpring], Source={x:Static starterNg:App.Loc}}" />
|
||||||
@@ -111,103 +112,121 @@
|
|||||||
<ComboBoxItem Tag="297" Content="{Binding [SeasonAutumn], Source={x:Static starterNg:App.Loc}}" />
|
<ComboBoxItem Tag="297" Content="{Binding [SeasonAutumn], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<ComboBoxItem Tag="15" Content="{Binding [SeasonWinter], Source={x:Static starterNg:App.Loc}}" />
|
<ComboBoxItem Tag="15" Content="{Binding [SeasonWinter], Source={x:Static starterNg:App.Loc}}" />
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
<TextBlock Grid.Column="4" Name="weatherDayDate"
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding [Day], Source={x:Static starterNg:App.Loc}}" />
|
Foreground="{StaticResource CarbonFgDim}" />
|
||||||
<NumericUpDown Grid.Row="2" Grid.Column="1" Name="weatherDay" Width="140"
|
|
||||||
HorizontalAlignment="Left" Minimum="0" Maximum="366"
|
|
||||||
Increment="1" FormatString="0"
|
|
||||||
ValueChanged="WeatherDay_OnChanged" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center"
|
|
||||||
Text="{Binding [Temperature], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
|
||||||
<Slider Name="weatherTemp" Minimum="-50" Maximum="50" Width="220"
|
|
||||||
TickFrequency="1" IsSnapToTickEnabled="True"
|
|
||||||
ValueChanged="Weather_OnSliderChanged" />
|
|
||||||
<TextBlock Name="weatherTempLabel" VerticalAlignment="Center" MinWidth="48" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center"
|
|
||||||
Text="{Binding [Visibility], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
|
||||||
<Slider Name="weatherFog" Minimum="0" Maximum="10000" Width="220"
|
|
||||||
TickFrequency="250" IsSnapToTickEnabled="True"
|
|
||||||
ValueChanged="Weather_OnSliderChanged" />
|
|
||||||
<TextBlock Name="weatherFogLabel" VerticalAlignment="Center" MinWidth="56" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="5" Grid.Column="0" VerticalAlignment="Center"
|
|
||||||
Text="{Binding [Overcast], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox Grid.Row="5" Grid.Column="1" Name="weatherOvercast" Width="220"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
SelectionChanged="Weather_OnSelectionChanged">
|
|
||||||
<ComboBoxItem Tag="-1.5" Content="{Binding [OvercastClear], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="0" Content="{Binding [OvercastFair], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="0.3" Content="{Binding [OvercastLight], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="0.7" Content="{Binding [OvercastModerate], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="1" Content="{Binding [OvercastHeavy], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="1.1" Content="{Binding [OvercastCloudy], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="1.5" Content="{Binding [OvercastStorm], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</ComboBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<TextBlock Name="weatherHint" Opacity="0.6" FontSize="11" TextWrapping="Wrap"
|
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center"
|
||||||
Text="{Binding [WeatherHint], Source={x:Static starterNg:App.Loc}}" />
|
Foreground="{StaticResource CarbonFgDim}"
|
||||||
</StackPanel>
|
Text="{Binding [Time], Source={x:Static starterNg:App.Loc}}" />
|
||||||
</ScrollViewer>
|
<Grid Grid.Row="2" Grid.Column="2" ColumnDefinitions="150,8,*">
|
||||||
|
<TimePicker Grid.Column="0" Name="weatherTime"
|
||||||
|
ClockIdentifier="24HourClock" MinuteIncrement="1"
|
||||||
|
MinWidth="0" VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectedTimeChanged="WeatherTime_OnChanged" />
|
||||||
|
<Button Grid.Column="2" Name="weatherTimeNow"
|
||||||
|
Classes="Flat" Cursor="Hand" VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
|
Click="WeatherTimeNow_OnClick"
|
||||||
|
Content="{Binding [CurrentTime], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center"
|
||||||
|
Foreground="{StaticResource CarbonFgDim}"
|
||||||
|
Text="{Binding [Temperature], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<Slider Grid.Row="4" Grid.Column="2" Name="weatherTemp"
|
||||||
|
Minimum="-50" Maximum="50"
|
||||||
|
TickFrequency="1" IsSnapToTickEnabled="True"
|
||||||
|
ValueChanged="Weather_OnSliderChanged" />
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="4" Name="weatherTempLabel"
|
||||||
|
VerticalAlignment="Center" TextAlignment="Right"
|
||||||
|
FontWeight="SemiBold" />
|
||||||
|
<DockPanel Grid.Row="5" Grid.Column="2" Margin="0,-6,0,0">
|
||||||
|
<TextBlock DockPanel.Dock="Right" FontSize="11"
|
||||||
|
Foreground="{StaticResource CarbonFgDim}" Text="+50 °C" />
|
||||||
|
<TextBlock FontSize="11"
|
||||||
|
Foreground="{StaticResource CarbonFgDim}" Text="-50 °C" />
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="7" Grid.Column="0" VerticalAlignment="Center"
|
||||||
|
Foreground="{StaticResource CarbonFgDim}"
|
||||||
|
Text="{Binding [Visibility], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<Slider Grid.Row="7" Grid.Column="2" Name="weatherFog"
|
||||||
|
Minimum="0" Maximum="1000"
|
||||||
|
TickFrequency="1" IsSnapToTickEnabled="True"
|
||||||
|
ValueChanged="Weather_OnSliderChanged" />
|
||||||
|
<TextBlock Grid.Row="7" Grid.Column="4" Name="weatherFogLabel"
|
||||||
|
VerticalAlignment="Center" TextAlignment="Right"
|
||||||
|
FontWeight="SemiBold" />
|
||||||
|
<DockPanel Grid.Row="8" Grid.Column="2" Margin="0,-6,0,0">
|
||||||
|
<TextBlock DockPanel.Dock="Right" FontSize="11"
|
||||||
|
Foreground="{StaticResource CarbonFgDim}"
|
||||||
|
Text="{Binding [VisibilityGood], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<TextBlock FontSize="11" Foreground="{StaticResource CarbonFgDim}"
|
||||||
|
Text="{Binding [VisibilityBad], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="10" Grid.Column="0" VerticalAlignment="Center"
|
||||||
|
Foreground="{StaticResource CarbonFgDim}"
|
||||||
|
Text="{Binding [Overcast], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox Grid.Row="10" Grid.Column="2" Name="weatherOvercast"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectionChanged="Weather_OnSelectionChanged">
|
||||||
|
<ComboBoxItem Tag="random" Content="{Binding [OvercastRandom], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="-1.5" Content="{Binding [OvercastClear], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="0" Content="{Binding [OvercastFair], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="0.3" Content="{Binding [OvercastLight], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="0.7" Content="{Binding [OvercastModerate], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="1" Content="{Binding [OvercastHeavy], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="1.1" Content="{Binding [OvercastCloudy], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="1.5" Content="{Binding [OvercastStorm], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<Border Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="5"
|
||||||
|
Height="1" VerticalAlignment="Center"
|
||||||
|
Background="{StaticResource CarbonBorderLight}" />
|
||||||
|
|
||||||
|
<Button Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3"
|
||||||
|
Name="weatherRestore" Classes="Flat" Cursor="Hand"
|
||||||
|
HorizontalAlignment="Right" MinWidth="180"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Click="WeatherRestore_OnClick"
|
||||||
|
Content="{Binding [RestoreWeather], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="{Binding [Timetable], Source={x:Static starterNg:App.Loc}}">
|
<TabItem Name="timetableTab" ToolTip.ShowOnDisabled="True"
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
Header="{Binding [Timetable], Source={x:Static starterNg:App.Loc}}">
|
||||||
<TextBlock Name="timetableContent" Margin="4"
|
<DockPanel LastChildFill="True" Margin="4">
|
||||||
FontFamily="{x:Static infra:MonoFont.Family}"
|
<ScrollViewer DockPanel.Dock="Bottom" HorizontalScrollBarVisibility="Auto"
|
||||||
FontSize="12" TextWrapping="NoWrap" />
|
VerticalScrollBarVisibility="Disabled" MaxHeight="72">
|
||||||
</ScrollViewer>
|
<StackPanel Name="attachmentsPanel" Orientation="Horizontal" Spacing="6" />
|
||||||
</TabItem>
|
</ScrollViewer>
|
||||||
<TabItem Name="infoTab" Header="{Binding [TabInfo], Source={x:Static starterNg:App.Loc}}">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<TextBlock Name="timetableContent"
|
||||||
<TextBox Name="faultListBox" Margin="4" IsReadOnly="True"
|
FontFamily="{x:Static infra:MonoFont.Family}"
|
||||||
AcceptsReturn="True" TextWrapping="Wrap"
|
FontSize="12" TextWrapping="NoWrap" />
|
||||||
FontFamily="{x:Static infra:MonoFont.Family}" FontSize="12" />
|
</ScrollViewer>
|
||||||
</ScrollViewer>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Right: scenery image (//$i) + scenery description (//$d) -->
|
|
||||||
<Grid Grid.Column="2" Grid.Row="0" RowSpacing="8">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<Border Grid.Row="0" ClipToBounds="True" VerticalAlignment="Top"
|
|
||||||
Background="{StaticResource CarbonPanelBg}"
|
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
|
||||||
Height="{Binding $self.Bounds.Width}">
|
|
||||||
<Image Name="sceneryImage" Stretch="Uniform" Margin="2" />
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<HeaderedContentControl Grid.Row="1" Theme="{StaticResource GroupBox}"
|
|
||||||
Header="{Binding [SceneryDescription], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<DockPanel LastChildFill="True">
|
|
||||||
<StackPanel Name="attachmentsPanel" DockPanel.Dock="Bottom" Spacing="2" Margin="0,6,0,0" />
|
|
||||||
<StackPanel Name="includesPanel" DockPanel.Dock="Bottom" Spacing="2" Margin="0,6,0,0" />
|
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
|
||||||
<TextBlock TextWrapping="Wrap" Name="sceneryDescription" />
|
|
||||||
</ScrollViewer>
|
|
||||||
</DockPanel>
|
|
||||||
</HeaderedContentControl>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Consist preview (full-width bottom, horizontally scrollable) -->
|
|
||||||
<HeaderedContentControl Grid.Row="1" Grid.ColumnSpan="3" Theme="{StaticResource GroupBox}"
|
<HeaderedContentControl Grid.Row="1" Grid.ColumnSpan="3" Theme="{StaticResource GroupBox}"
|
||||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<TextBlock Name="trainStats" DockPanel.Dock="Top" FontSize="12"
|
<StackPanel DockPanel.Dock="Top" Name="trainStats" Orientation="Horizontal"
|
||||||
Opacity="0.85" Margin="0,0,0,6" TextTrimming="CharacterEllipsis" />
|
VerticalAlignment="Center" Margin="0,0,0,6" />
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
<ScrollViewer Name="consistScroll"
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Name="consistStack" />
|
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||||
|
<Panel>
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
|
Name="consistStack" />
|
||||||
|
<Canvas Name="consistOverlay" IsHitTestVisible="False" />
|
||||||
|
</Panel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</HeaderedContentControl>
|
</HeaderedContentControl>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ using System.Text;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Animation;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
@@ -37,9 +39,7 @@ public partial class Scenarios : UserControl
|
|||||||
Sceneries = GameData.Instance.Sceneries;
|
Sceneries = GameData.Instance.Sceneries;
|
||||||
|
|
||||||
_loadingFilters = true;
|
_loadingFilters = true;
|
||||||
showAiCheck.IsChecked = StarterNG.Classes.Settings.Instance.ShowAiVehicles;
|
archivalSwitch.IsChecked = !StarterNG.Classes.Settings.Instance.ShowArchivalSceneries;
|
||||||
drivableOnlyCheck.IsChecked = StarterNG.Classes.Settings.Instance.DrivableOnly;
|
|
||||||
archivalSwitch.IsChecked = StarterNG.Classes.Settings.Instance.ShowArchivalSceneries;
|
|
||||||
_loadingFilters = false;
|
_loadingFilters = false;
|
||||||
|
|
||||||
BuildSceneryTree();
|
BuildSceneryTree();
|
||||||
@@ -63,7 +63,7 @@ public partial class Scenarios : UserControl
|
|||||||
{
|
{
|
||||||
sceneryList.Items.Clear();
|
sceneryList.Items.Clear();
|
||||||
|
|
||||||
bool showArchival = archivalSwitch.IsChecked ?? true;
|
bool showArchival = archivalSwitch.IsChecked != true;
|
||||||
var groupNodes = new Dictionary<string, TreeViewItem>();
|
var groupNodes = new Dictionary<string, TreeViewItem>();
|
||||||
var topLevel = new List<TreeViewItem>();
|
var topLevel = new List<TreeViewItem>();
|
||||||
|
|
||||||
@@ -213,15 +213,15 @@ public partial class Scenarios : UserControl
|
|||||||
{
|
{
|
||||||
vehicleList.Items.Clear();
|
vehicleList.Items.Clear();
|
||||||
|
|
||||||
bool showAi = showAiCheck.IsChecked ?? true;
|
bool showAi = StarterNG.Classes.Settings.Instance.ShowAiVehicles;
|
||||||
bool drivableOnly = drivableOnlyCheck.IsChecked ?? true;
|
bool drivableOnly = StarterNG.Classes.Settings.Instance.DrivableOnly;
|
||||||
|
|
||||||
for (int i = 0; i < scn.Trainsets.Count; i++)
|
for (int i = 0; i < scn.Trainsets.Count; i++)
|
||||||
{
|
{
|
||||||
var trainset = scn.Trainsets[i];
|
var trainset = scn.Trainsets[i];
|
||||||
|
|
||||||
if (IsAiTrainset(trainset) && !showAi) continue;
|
if (IsAiTrainset(trainset) && !showAi) continue;
|
||||||
if (trainset.Decor && drivableOnly) continue;
|
if (drivableOnly && !UData.StaffedTrain(trainset)) continue;
|
||||||
|
|
||||||
var listItem = new ListBoxItem
|
var listItem = new ListBoxItem
|
||||||
{
|
{
|
||||||
@@ -244,23 +244,11 @@ public partial class Scenarios : UserControl
|
|||||||
!string.IsNullOrEmpty(trainset.Description) &&
|
!string.IsNullOrEmpty(trainset.Description) &&
|
||||||
trainset.Description.TrimStart().StartsWith("-", StringComparison.Ordinal);
|
trainset.Description.TrimStart().StartsWith("-", StringComparison.Ordinal);
|
||||||
|
|
||||||
private void VehicleFilter_OnChanged(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_loadingFilters) return;
|
|
||||||
|
|
||||||
StarterNG.Classes.Settings.Instance.ShowAiVehicles = showAiCheck.IsChecked ?? true;
|
|
||||||
StarterNG.Classes.Settings.Instance.DrivableOnly = drivableOnlyCheck.IsChecked ?? true;
|
|
||||||
StarterNG.Classes.Settings.Instance.Save();
|
|
||||||
|
|
||||||
if (sceneryList.SelectedItem is TreeViewItem { Tag: int tag } && tag < Sceneries.Count)
|
|
||||||
PopulateVehicleList(Sceneries[tag]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ArchivalSwitch_OnChanged(object? sender, RoutedEventArgs e)
|
private void ArchivalSwitch_OnChanged(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_loadingFilters) return;
|
if (_loadingFilters) return;
|
||||||
|
|
||||||
StarterNG.Classes.Settings.Instance.ShowArchivalSceneries = archivalSwitch.IsChecked ?? true;
|
StarterNG.Classes.Settings.Instance.ShowArchivalSceneries = archivalSwitch.IsChecked != true;
|
||||||
StarterNG.Classes.Settings.Instance.Save();
|
StarterNG.Classes.Settings.Instance.Save();
|
||||||
|
|
||||||
BuildSceneryTree();
|
BuildSceneryTree();
|
||||||
@@ -329,9 +317,10 @@ public partial class Scenarios : UserControl
|
|||||||
{
|
{
|
||||||
sceneryImage.Source = new Bitmap(imagePath);
|
sceneryImage.Source = new Bitmap(imagePath);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
sceneryImage.Source = null;
|
sceneryImage.Source = null;
|
||||||
|
StarterNG.Infrastructure.Diagnostics.Log($"Scenery image {imagePath}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -340,7 +329,6 @@ public partial class Scenarios : UserControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
PopulateAttachments(scenery);
|
PopulateAttachments(scenery);
|
||||||
PopulateIncludes(scenery);
|
|
||||||
PopulateFaults(scenery);
|
PopulateFaults(scenery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,8 +356,8 @@ public partial class Scenarios : UserControl
|
|||||||
lines.Add($"# unknown loose texture: {v.Name} ({v.SkinFile})");
|
lines.Add($"# unknown loose texture: {v.Name} ({v.SkinFile})");
|
||||||
}
|
}
|
||||||
|
|
||||||
faultListBox.Text = string.Join(Environment.NewLine, lines);
|
if (lines.Count > 0)
|
||||||
infoTab.IsVisible = lines.Count > 0 || scenery.Includes.Any(i => i.Kind != 2);
|
StarterNG.Infrastructure.Diagnostics.Log(lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopulateAttachments(Scenery scenery)
|
private void PopulateAttachments(Scenery scenery)
|
||||||
@@ -392,38 +380,6 @@ public partial class Scenarios : UserControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopulateIncludes(Scenery scenery)
|
|
||||||
{
|
|
||||||
includesPanel.Children.Clear();
|
|
||||||
var opts = scenery.Includes.Where(i => i.Kind != 2).ToList();
|
|
||||||
if (opts.Count == 0) return;
|
|
||||||
|
|
||||||
includesPanel.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = App.Loc["OptionalIncludes"],
|
|
||||||
FontWeight = Avalonia.Media.FontWeight.Bold,
|
|
||||||
FontSize = 12,
|
|
||||||
Margin = new Thickness(0, 0, 0, 2)
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var inc in opts)
|
|
||||||
{
|
|
||||||
var check = new CheckBox
|
|
||||||
{
|
|
||||||
Content = $"{inc.Desc} ({inc.FilePath})",
|
|
||||||
IsChecked = inc.Selected,
|
|
||||||
FontSize = 11,
|
|
||||||
Tag = inc
|
|
||||||
};
|
|
||||||
check.IsCheckedChanged += (_, _) =>
|
|
||||||
{
|
|
||||||
if (check.Tag is SceneryInclude s)
|
|
||||||
s.Selected = check.IsChecked == true;
|
|
||||||
};
|
|
||||||
includesPanel.Children.Add(check);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OpenAttachment(string relativePath)
|
private static void OpenAttachment(string relativePath)
|
||||||
{
|
{
|
||||||
string full = Path.GetFullPath(relativePath);
|
string full = Path.GetFullPath(relativePath);
|
||||||
@@ -453,9 +409,12 @@ public partial class Scenarios : UserControl
|
|||||||
RefreshSelectedConsist();
|
RefreshSelectedConsist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _reloading;
|
||||||
|
|
||||||
private async void ReloadSceneries_OnClick(object? sender, RoutedEventArgs e)
|
private async void ReloadSceneries_OnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
reloadButton.IsEnabled = false;
|
if (_reloading) return;
|
||||||
|
_reloading = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string? selected = AppState.Instance.CurrentScenery?.Path;
|
string? selected = AppState.Instance.CurrentScenery?.Path;
|
||||||
@@ -471,22 +430,94 @@ public partial class Scenarios : UserControl
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
reloadButton.IsEnabled = true;
|
_reloading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Action<Scenery>? RandomizeTexturesRequested;
|
||||||
|
|
||||||
|
private void RandomTexturesButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (AppState.Instance.CurrentScenery is { } scenery)
|
||||||
|
RandomizeTexturesRequested?.Invoke(scenery);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshConsistView()
|
||||||
|
{
|
||||||
|
RefreshVehicleLabels();
|
||||||
|
if (AppState.Instance.CurrentTrainset is { } trainset)
|
||||||
|
ShowConsist(trainset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Avalonia.Media.IBrush SelectedVehicleBrush =
|
||||||
|
new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#41C400"));
|
||||||
|
|
||||||
|
private static readonly Avalonia.Media.IBrush MissingMiniBrush =
|
||||||
|
new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#22808080"));
|
||||||
|
|
||||||
|
private static Control MissingMiniBox(string? label, int height) => new Border
|
||||||
|
{
|
||||||
|
Height = height,
|
||||||
|
Width = height * 2,
|
||||||
|
Background = MissingMiniBrush,
|
||||||
|
CornerRadius = new Avalonia.CornerRadius(4),
|
||||||
|
Child = new TextBlock
|
||||||
|
{
|
||||||
|
Text = label,
|
||||||
|
FontSize = 10,
|
||||||
|
TextWrapping = Avalonia.Media.TextWrapping.Wrap,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
TextAlignment = Avalonia.Media.TextAlignment.Center
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly Dictionary<string, Bitmap?> _miniCache = new();
|
||||||
|
|
||||||
|
private Bitmap? LoadMini(string? name, int height)
|
||||||
|
{
|
||||||
|
string key = $"{name}@{height}";
|
||||||
|
if (_miniCache.TryGetValue(key, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
Bitmap? bmp = null;
|
||||||
|
string? path = VehicleDatabase.MiniPath(name);
|
||||||
|
if (path != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var fs = File.OpenRead(path);
|
||||||
|
bmp = Bitmap.DecodeToHeight(fs, height);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StarterNG.Infrastructure.Diagnostics.Log($"Mini {path}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_miniCache[key] = bmp;
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenSceneryDir_OnClick(object? sender, RoutedEventArgs e)
|
private void OpenSceneryDir_OnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var scn = AppState.Instance.CurrentScenery;
|
var scn = AppState.Instance.CurrentScenery;
|
||||||
string dir = scn != null
|
string dir = scn != null
|
||||||
? (Path.GetDirectoryName(Path.GetFullPath(scn.Path)) ?? "scenery")
|
? (Path.GetDirectoryName(Path.GetFullPath(scn.Path)) ?? "scenery")
|
||||||
: Path.GetFullPath("scenery");
|
: Path.GetFullPath("scenery");
|
||||||
if (!Directory.Exists(dir)) return;
|
if (!Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(string.Format(App.Loc["FaultFileNotFound"], dir));
|
||||||
|
return;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(
|
||||||
|
$"{dir}{Environment.NewLine}{Environment.NewLine}{App.Loc["FaultDetail"]} {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
@@ -518,11 +549,13 @@ public partial class Scenarios : UserControl
|
|||||||
{
|
{
|
||||||
weatherForm.IsEnabled = true;
|
weatherForm.IsEnabled = true;
|
||||||
weatherTime.SelectedTime = ParseTime(scenery.ScenarioTimeOverride);
|
weatherTime.SelectedTime = ParseTime(scenery.ScenarioTimeOverride);
|
||||||
weatherDay.Value = scenery.Day;
|
weatherDate.SelectedDate = DateOfDayOfYear(scenery.Day);
|
||||||
weatherTemp.Value = scenery.Temperature;
|
weatherTemp.Value = scenery.Temperature;
|
||||||
weatherFog.Value = System.Math.Clamp(scenery.FogEnd, 0, 10000);
|
weatherFog.Value = SliderFromFog(scenery.FogEnd);
|
||||||
SelectByTag(weatherSeason, scenery.Day.ToString());
|
SelectByTag(weatherSeason, scenery.Day.ToString(), excludeFromNearest: "0");
|
||||||
SelectByTag(weatherOvercast, scenery.Overcast.ToString(CultureInfo.InvariantCulture));
|
SelectByTag(weatherOvercast, scenery.OvercastRandom
|
||||||
|
? RandomTag
|
||||||
|
: scenery.Overcast.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
ApplyTodayPreview(scenery.Day == 0);
|
ApplyTodayPreview(scenery.Day == 0);
|
||||||
UpdateWeatherLabels();
|
UpdateWeatherLabels();
|
||||||
@@ -542,12 +575,16 @@ public partial class Scenarios : UserControl
|
|||||||
if (weatherTime.SelectedTime is { } t)
|
if (weatherTime.SelectedTime is { } t)
|
||||||
scn.ScenarioTimeOverride = $"{t.Hours:D2}:{t.Minutes:D2}";
|
scn.ScenarioTimeOverride = $"{t.Hours:D2}:{t.Minutes:D2}";
|
||||||
|
|
||||||
scn.Day = _todaySeason ? 0 : (int)(weatherDay.Value ?? 0);
|
scn.Day = _todaySeason ? 0 : SelectedDayOfYear();
|
||||||
scn.Temperature = weatherTemp.Value;
|
scn.Temperature = weatherTemp.Value;
|
||||||
scn.FogEnd = (int)weatherFog.Value;
|
scn.FogEnd = FogFromSlider(weatherFog.Value);
|
||||||
if ((weatherOvercast.SelectedItem as ComboBoxItem)?.Tag is string ov &&
|
if ((weatherOvercast.SelectedItem as ComboBoxItem)?.Tag is string ov)
|
||||||
double.TryParse(ov, System.Globalization.NumberStyles.Float, CultureInfo.InvariantCulture, out double oc))
|
{
|
||||||
scn.Overcast = oc;
|
scn.OvercastRandom = ov == RandomTag;
|
||||||
|
if (!scn.OvercastRandom &&
|
||||||
|
double.TryParse(ov, NumberStyles.Float, CultureInfo.InvariantCulture, out double oc))
|
||||||
|
scn.Overcast = oc;
|
||||||
|
}
|
||||||
|
|
||||||
scn.WeatherDirty = true;
|
scn.WeatherDirty = true;
|
||||||
UpdateWeatherLabels();
|
UpdateWeatherLabels();
|
||||||
@@ -556,18 +593,85 @@ public partial class Scenarios : UserControl
|
|||||||
private void UpdateWeatherLabels()
|
private void UpdateWeatherLabels()
|
||||||
{
|
{
|
||||||
weatherTempLabel.Text = $"{(int)weatherTemp.Value} °C";
|
weatherTempLabel.Text = $"{(int)weatherTemp.Value} °C";
|
||||||
weatherFogLabel.Text = $"{(int)weatherFog.Value} m";
|
weatherFogLabel.Text = FormatFog(FogFromSlider(weatherFog.Value));
|
||||||
|
weatherDayDate.Text = string.Format(LanguageCulture, App.Loc["DayOfYear"], SelectedDayOfYear());
|
||||||
|
|
||||||
|
weatherRestore.IsEnabled = _currentScenery?.WeatherChanged ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SelectByTag(ComboBox combo, string tag)
|
private static int FogFromSlider(double position)
|
||||||
{
|
{
|
||||||
|
double metres = Scenery.FogMin *
|
||||||
|
System.Math.Pow((double)Scenery.FogMax / Scenery.FogMin, position / 1000.0);
|
||||||
|
int step = metres < 100 ? 5 : metres < 1000 ? 10 : 100;
|
||||||
|
return System.Math.Clamp((int)(System.Math.Round(metres / step) * step),
|
||||||
|
Scenery.FogMin, Scenery.FogMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double SliderFromFog(int metres)
|
||||||
|
{
|
||||||
|
double clamped = System.Math.Clamp(metres, Scenery.FogMin, Scenery.FogMax);
|
||||||
|
return 1000.0 * System.Math.Log(clamped / Scenery.FogMin) /
|
||||||
|
System.Math.Log((double)Scenery.FogMax / Scenery.FogMin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatFog(int metres) =>
|
||||||
|
metres < 1000
|
||||||
|
? $"{metres} m"
|
||||||
|
: string.Format(LanguageCulture, "{0:0.#} km", metres / 1000.0);
|
||||||
|
|
||||||
|
private static CultureInfo LanguageCulture
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string code = App.Loc.CurrentLangCode;
|
||||||
|
if (code is "en" or "")
|
||||||
|
return CultureInfo.InvariantCulture;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return CultureInfo.GetCultureInfo(code == "cz" ? "cs" : code);
|
||||||
|
}
|
||||||
|
catch (CultureNotFoundException)
|
||||||
|
{
|
||||||
|
return CultureInfo.InvariantCulture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string RandomTag = "random";
|
||||||
|
|
||||||
|
private static void SelectByTag(ComboBox combo, string tag, string? excludeFromNearest = null)
|
||||||
|
{
|
||||||
|
ComboBoxItem? first = null;
|
||||||
|
ComboBoxItem? nearest = null;
|
||||||
|
double bestDistance = double.MaxValue;
|
||||||
|
bool haveValue = double.TryParse(tag, NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||||
|
out double value);
|
||||||
|
|
||||||
foreach (var obj in combo.Items)
|
foreach (var obj in combo.Items)
|
||||||
if (obj is ComboBoxItem item && (item.Tag as string) == tag)
|
{
|
||||||
|
if (obj is not ComboBoxItem item)
|
||||||
|
continue;
|
||||||
|
first ??= item;
|
||||||
|
if ((item.Tag as string) == tag)
|
||||||
{
|
{
|
||||||
combo.SelectedItem = item;
|
combo.SelectedItem = item;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
combo.SelectedItem = null;
|
if (haveValue && item.Tag is string itemTag && itemTag != excludeFromNearest &&
|
||||||
|
double.TryParse(itemTag, NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||||
|
out double itemValue))
|
||||||
|
{
|
||||||
|
double distance = System.Math.Abs(itemValue - value);
|
||||||
|
if (distance < bestDistance)
|
||||||
|
{
|
||||||
|
bestDistance = distance;
|
||||||
|
nearest = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
combo.SelectedItem = nearest ?? first;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WeatherTime_OnChanged(object? sender, TimePickerSelectedValueChangedEventArgs e) => CaptureWeather();
|
private void WeatherTime_OnChanged(object? sender, TimePickerSelectedValueChangedEventArgs e) => CaptureWeather();
|
||||||
@@ -600,12 +704,11 @@ public partial class Scenarios : UserControl
|
|||||||
private void ApplyTodayPreview(bool today)
|
private void ApplyTodayPreview(bool today)
|
||||||
{
|
{
|
||||||
_todaySeason = today;
|
_todaySeason = today;
|
||||||
weatherDay.IsEnabled = !today;
|
|
||||||
if (today)
|
if (today)
|
||||||
{
|
{
|
||||||
bool prev = _loadingWeather;
|
bool prev = _loadingWeather;
|
||||||
_loadingWeather = true;
|
_loadingWeather = true;
|
||||||
weatherDay.Value = DateTime.Now.DayOfYear;
|
weatherDate.SelectedDate = DateOfDayOfYear(0);
|
||||||
_loadingWeather = prev;
|
_loadingWeather = prev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -614,7 +717,28 @@ public partial class Scenarios : UserControl
|
|||||||
|
|
||||||
private void Weather_OnSliderChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e) => CaptureWeather();
|
private void Weather_OnSliderChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e) => CaptureWeather();
|
||||||
|
|
||||||
private void WeatherDay_OnChanged(object? sender, NumericUpDownValueChangedEventArgs e) => CaptureWeather();
|
private void WeatherDate_OnChanged(object? sender, DatePickerSelectedValueChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_loadingWeather)
|
||||||
|
{
|
||||||
|
_todaySeason = false;
|
||||||
|
_loadingWeather = true;
|
||||||
|
SelectByTag(weatherSeason, SelectedDayOfYear().ToString(), excludeFromNearest: "0");
|
||||||
|
_loadingWeather = false;
|
||||||
|
}
|
||||||
|
CaptureWeather();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTimeOffset DateOfDayOfYear(int day)
|
||||||
|
{
|
||||||
|
int year = DateTime.Now.Year;
|
||||||
|
int last = DateTime.IsLeapYear(year) ? 366 : 365;
|
||||||
|
int d = System.Math.Clamp(day <= 0 ? DateTime.Now.DayOfYear : day, 1, last);
|
||||||
|
return new DateTimeOffset(new DateTime(year, 1, 1).AddDays(d - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SelectedDayOfYear() =>
|
||||||
|
weatherDate.SelectedDate is { } date ? date.DayOfYear : DateTime.Now.DayOfYear;
|
||||||
|
|
||||||
private void WeatherSeason_OnChanged(object? sender, SelectionChangedEventArgs e)
|
private void WeatherSeason_OnChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -631,7 +755,7 @@ public partial class Scenarios : UserControl
|
|||||||
{
|
{
|
||||||
ApplyTodayPreview(false);
|
ApplyTodayPreview(false);
|
||||||
_loadingWeather = true;
|
_loadingWeather = true;
|
||||||
weatherDay.Value = day;
|
weatherDate.SelectedDate = DateOfDayOfYear(day);
|
||||||
_loadingWeather = false;
|
_loadingWeather = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -644,19 +768,33 @@ public partial class Scenarios : UserControl
|
|||||||
if (path is null)
|
if (path is null)
|
||||||
{
|
{
|
||||||
timetableContent.Text = App.Loc["NoTimetable"];
|
timetableContent.Text = App.Loc["NoTimetable"];
|
||||||
|
UpdateTimetableTab(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
timetableContent.Text = File.ReadAllText(path, Encoding.GetEncoding(1250));
|
timetableContent.Text = File.ReadAllText(path, Encoding.GetEncoding(1250));
|
||||||
|
UpdateTimetableTab(true);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
timetableContent.Text = App.Loc["NoTimetable"];
|
timetableContent.Text = App.Loc["NoTimetable"];
|
||||||
|
UpdateTimetableTab(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateTimetableTab(bool hasTimetable)
|
||||||
|
{
|
||||||
|
bool usable = hasTimetable || attachmentsPanel.Children.Count > 0;
|
||||||
|
|
||||||
|
timetableTab.IsEnabled = usable;
|
||||||
|
ToolTip.SetTip(timetableTab, usable ? null : App.Loc["NoTimetable"]);
|
||||||
|
|
||||||
|
if (!usable && timetableTab.IsSelected && timetableTab.Parent is TabControl tabs)
|
||||||
|
tabs.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static string? ResolveTimetablePath(Scenery scenery, string? name)
|
private static string? ResolveTimetablePath(Scenery scenery, string? name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name) ||
|
if (string.IsNullOrWhiteSpace(name) ||
|
||||||
@@ -677,27 +815,127 @@ public partial class Scenarios : UserControl
|
|||||||
return candidates.FirstOrDefault(File.Exists);
|
return candidates.FirstOrDefault(File.Exists);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const double ConsistDragThreshold = 6;
|
||||||
|
|
||||||
|
private Trainset? _stripTrainset;
|
||||||
|
private int _pressIndex = -1;
|
||||||
|
private Point _pressPoint;
|
||||||
|
private bool _dragging;
|
||||||
|
private bool _stripWired;
|
||||||
|
|
||||||
|
private int _dragBlockStart = -1;
|
||||||
|
private int _dragBlockLength;
|
||||||
|
private double _dragBlockWidth = 32;
|
||||||
|
private double _grabOffset;
|
||||||
|
private Control? _carried;
|
||||||
|
|
||||||
|
private readonly List<int> _reducedToVehicle = new();
|
||||||
|
private readonly List<int> _unitStarts = new();
|
||||||
|
|
||||||
|
private readonly Infrastructure.DropGap _dropGap = new();
|
||||||
|
private readonly Infrastructure.DropIndexTracker _dropTracker = new();
|
||||||
|
private readonly Infrastructure.EdgeScroller _edgeScroller = new();
|
||||||
|
|
||||||
private void ShowConsist(Trainset trainset)
|
private void ShowConsist(Trainset trainset)
|
||||||
{
|
{
|
||||||
consistStack.Children.Clear();
|
consistStack.Children.Clear();
|
||||||
|
_stripTrainset = trainset;
|
||||||
|
_dropGap.Forget();
|
||||||
|
|
||||||
|
if (!_stripWired)
|
||||||
|
{
|
||||||
|
_stripWired = true;
|
||||||
|
consistStack.PointerMoved += Strip_OnPointerMoved;
|
||||||
|
consistStack.PointerReleased += Strip_OnPointerReleased;
|
||||||
|
consistStack.PointerCaptureLost += (_, _) => CancelConsistDrag();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextMenu VehicleMenu(Dynamic vehicle) => ConsistContextMenu.Build(
|
||||||
|
consistStack,
|
||||||
|
() => trainset,
|
||||||
|
vehicles =>
|
||||||
|
{
|
||||||
|
trainset.Vehicles = vehicles;
|
||||||
|
RefreshVehicleLabels();
|
||||||
|
ShowConsist(trainset);
|
||||||
|
UpdateTrainStats(trainset);
|
||||||
|
},
|
||||||
|
removeAll: null,
|
||||||
|
getVehicle: () => vehicle,
|
||||||
|
removeVehicle: v =>
|
||||||
|
{
|
||||||
|
int gap = trainset.Vehicles.IndexOf(v);
|
||||||
|
trainset.Vehicles.Remove(v);
|
||||||
|
|
||||||
|
bool wasPicked = !string.IsNullOrEmpty(v.Name) &&
|
||||||
|
string.Equals(v.Name, AppState.Instance.StartingVehicleName,
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (wasPicked && trainset.Vehicles.Count > 0)
|
||||||
|
{
|
||||||
|
int next = Math.Clamp(gap, 0, trainset.Vehicles.Count - 1);
|
||||||
|
AppState.Instance.StartingVehicleName = trainset.Vehicles[next].Name;
|
||||||
|
AppState.Instance.NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshVehicleLabels();
|
||||||
|
ShowConsist(trainset);
|
||||||
|
UpdateTrainStats(trainset);
|
||||||
|
});
|
||||||
|
|
||||||
var db = GameData.Instance.Vehicles;
|
var db = GameData.Instance.Vehicles;
|
||||||
foreach (var train in trainset.Vehicles)
|
foreach (var train in trainset.Vehicles)
|
||||||
{
|
{
|
||||||
|
|
||||||
string miniName = db.MiniForSkin(train.SkinFile) ?? train.SkinFile;
|
string miniName = db.MiniForSkin(train.SkinFile) ?? train.SkinFile;
|
||||||
string? path = VehicleDatabase.MiniPath(miniName);
|
if (!VehicleDatabase.HasMini(miniName) && VehicleDatabase.HasMini(train.SkinFile))
|
||||||
if (path is null)
|
miniName = train.SkinFile;
|
||||||
continue;
|
|
||||||
|
|
||||||
int thumbH = StarterNG.Classes.Settings.Instance.LargeThumbnails ? 64 : 32;
|
int thumbH = StarterNG.Classes.Settings.Instance.LargeThumbnails ? 64 : 32;
|
||||||
consistStack.Children.Add(new Image
|
|
||||||
|
var bmp = LoadMini(miniName, thumbH);
|
||||||
|
Control visual = bmp is not null
|
||||||
|
? 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) &&
|
||||||
|
string.Equals(train.Name, AppState.Instance.StartingVehicleName,
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var slot = new Border
|
||||||
{
|
{
|
||||||
Source = new Bitmap(path),
|
Transitions = new Transitions
|
||||||
Height = thumbH,
|
{
|
||||||
Stretch = Avalonia.Media.Stretch.Uniform,
|
new DoubleTransition
|
||||||
Margin = new Thickness(0)
|
{
|
||||||
});
|
Property = OpacityProperty,
|
||||||
|
Duration = TimeSpan.FromMilliseconds(120)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Child = visual,
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
BorderThickness = new Thickness(0, 2),
|
||||||
|
BorderBrush = selected ? SelectedVehicleBrush : Avalonia.Media.Brushes.Transparent,
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand),
|
||||||
|
ContextMenu = VehicleMenu(train)
|
||||||
|
};
|
||||||
|
ToolTip.SetTip(slot, train.Name);
|
||||||
|
|
||||||
|
int slotIndex = consistStack.Children.Count;
|
||||||
|
|
||||||
|
slot.PointerPressed += (_, ev) =>
|
||||||
|
{
|
||||||
|
if (!ev.GetCurrentPoint(slot).Properties.IsLeftButtonPressed) return;
|
||||||
|
_pressIndex = slotIndex;
|
||||||
|
_pressPoint = ev.GetPosition(consistStack);
|
||||||
|
_dragging = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
consistStack.Children.Add(slot);
|
||||||
}
|
}
|
||||||
string desc = trainset.Description ?? "";
|
string desc = trainset.Description ?? "";
|
||||||
if (desc.TrimStart().StartsWith('-'))
|
if (desc.TrimStart().StartsWith('-'))
|
||||||
@@ -706,22 +944,256 @@ public partial class Scenarios : UserControl
|
|||||||
UpdateTrainStats(trainset);
|
UpdateTrainStats(trainset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Strip_OnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
var point = e.GetPosition(consistStack);
|
||||||
|
|
||||||
|
if (!_dragging)
|
||||||
|
{
|
||||||
|
if (_pressIndex < 0 || !e.GetCurrentPoint(consistStack).Properties.IsLeftButtonPressed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var delta = point - _pressPoint;
|
||||||
|
if (Math.Abs(delta.X) < ConsistDragThreshold && Math.Abs(delta.Y) < ConsistDragThreshold)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BeginConsistDrag(e);
|
||||||
|
if (!_dragging) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas.SetLeft(_carried!, point.X - _grabOffset);
|
||||||
|
|
||||||
|
double pointerX = e.GetPosition(consistScroll).X;
|
||||||
|
if (_dropTracker.Update(pointerX + consistScroll.Offset.X))
|
||||||
|
{
|
||||||
|
int reduced = ReducedTarget();
|
||||||
|
_dropGap.Show(consistStack,
|
||||||
|
reduced < _reducedToVehicle.Count
|
||||||
|
? _reducedToVehicle[reduced]
|
||||||
|
: consistStack.Children.Count,
|
||||||
|
_dragBlockWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
_edgeScroller.Update(consistScroll, pointerX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Strip_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_dragging)
|
||||||
|
{
|
||||||
|
int index = _pressIndex;
|
||||||
|
_pressIndex = -1;
|
||||||
|
|
||||||
|
if (e.InitialPressMouseButton != MouseButton.Left ||
|
||||||
|
index < 0 || _stripTrainset is not { } clicked ||
|
||||||
|
index >= clicked.Vehicles.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AppState.Instance.StartingVehicleName = clicked.Vehicles[index].Name;
|
||||||
|
AppState.Instance.NotifyChanged();
|
||||||
|
ShowConsist(clicked);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int target = _dropTracker.Index >= 0 ? ReducedTarget() : -1;
|
||||||
|
int start = _dragBlockStart;
|
||||||
|
int length = _dragBlockLength;
|
||||||
|
var trainset = _stripTrainset;
|
||||||
|
|
||||||
|
EndConsistDrag();
|
||||||
|
|
||||||
|
if (trainset != null && start >= 0 && target >= 0)
|
||||||
|
MoveConsistBlock(trainset, start, length, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginConsistDrag(PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (_stripTrainset is not { } trainset || _pressIndex < 0 ||
|
||||||
|
_pressIndex >= trainset.Vehicles.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_dragBlockStart = SetStart(trainset.Vehicles, _pressIndex);
|
||||||
|
_dragBlockLength = SetLength(trainset.Vehicles, _dragBlockStart);
|
||||||
|
if (_dragBlockStart + _dragBlockLength > consistStack.Children.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var first = consistStack.Children[_dragBlockStart];
|
||||||
|
var blockBounds = first.Bounds;
|
||||||
|
_dragBlockWidth = 0;
|
||||||
|
for (int i = _dragBlockStart; i < _dragBlockStart + _dragBlockLength; i++)
|
||||||
|
_dragBlockWidth += consistStack.Children[i].Bounds.Width;
|
||||||
|
_dragBlockWidth = Math.Max(_dragBlockWidth, 16);
|
||||||
|
_grabOffset = Math.Clamp(_pressPoint.X - blockBounds.Left, 0, _dragBlockWidth);
|
||||||
|
|
||||||
|
CaptureDropTargets(trainset);
|
||||||
|
|
||||||
|
_carried = BuildCarriedVisual(blockBounds.Height);
|
||||||
|
Canvas.SetTop(_carried, blockBounds.Top);
|
||||||
|
Canvas.SetLeft(_carried, blockBounds.Left);
|
||||||
|
consistOverlay.Children.Add(_carried);
|
||||||
|
|
||||||
|
for (int i = _dragBlockStart; i < _dragBlockStart + _dragBlockLength; i++)
|
||||||
|
consistStack.Children[i].IsVisible = false;
|
||||||
|
|
||||||
|
_dragging = true;
|
||||||
|
e.Pointer.Capture(consistStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureDropTargets(Trainset trainset)
|
||||||
|
{
|
||||||
|
var vehicles = trainset.Vehicles;
|
||||||
|
|
||||||
|
_reducedToVehicle.Clear();
|
||||||
|
for (int i = 0; i < vehicles.Count && i < consistStack.Children.Count; i++)
|
||||||
|
if (i < _dragBlockStart || i >= _dragBlockStart + _dragBlockLength)
|
||||||
|
_reducedToVehicle.Add(i);
|
||||||
|
|
||||||
|
_unitStarts.Clear();
|
||||||
|
var midpoints = new List<double>();
|
||||||
|
|
||||||
|
int r = 0;
|
||||||
|
while (r < _reducedToVehicle.Count)
|
||||||
|
{
|
||||||
|
int length = 1;
|
||||||
|
while (r + length < _reducedToVehicle.Count &&
|
||||||
|
_reducedToVehicle[r + length] == _reducedToVehicle[r + length - 1] + 1 &&
|
||||||
|
HoldsNext(vehicles[_reducedToVehicle[r + length - 1]]))
|
||||||
|
length++;
|
||||||
|
|
||||||
|
double left = ChildEdge(_reducedToVehicle[r], right: false);
|
||||||
|
double right = ChildEdge(_reducedToVehicle[r + length - 1], right: true);
|
||||||
|
midpoints.Add((left + right) / 2);
|
||||||
|
_unitStarts.Add(r);
|
||||||
|
r += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dropTracker.Capture(midpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double ChildEdge(int childIndex, bool right)
|
||||||
|
{
|
||||||
|
var bounds = consistStack.Children[childIndex].Bounds;
|
||||||
|
double edge = right ? bounds.Right : bounds.Left;
|
||||||
|
return childIndex > _dragBlockStart ? edge - _dragBlockWidth : edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ReducedTarget()
|
||||||
|
{
|
||||||
|
int unit = _dropTracker.Index;
|
||||||
|
if (unit < 0) return -1;
|
||||||
|
return unit < _unitStarts.Count ? _unitStarts[unit] : _reducedToVehicle.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control BuildCarriedVisual(double height)
|
||||||
|
{
|
||||||
|
var panel = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal };
|
||||||
|
|
||||||
|
for (int i = _dragBlockStart; i < _dragBlockStart + _dragBlockLength; i++)
|
||||||
|
{
|
||||||
|
var source = consistStack.Children[i];
|
||||||
|
Control copy = source is Border { Child: Image image }
|
||||||
|
? new Image { Source = image.Source, Height = image.Height, Stretch = image.Stretch }
|
||||||
|
: new Border
|
||||||
|
{
|
||||||
|
Width = source.Bounds.Width,
|
||||||
|
Height = source.Bounds.Height,
|
||||||
|
Background = MissingMiniBrush
|
||||||
|
};
|
||||||
|
panel.Children.Add(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Border
|
||||||
|
{
|
||||||
|
Child = panel,
|
||||||
|
Height = height,
|
||||||
|
Opacity = 0.85,
|
||||||
|
BorderThickness = new Thickness(0, 2),
|
||||||
|
BorderBrush = SelectedVehicleBrush
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelConsistDrag()
|
||||||
|
{
|
||||||
|
if (!_dragging) return;
|
||||||
|
EndConsistDrag();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndConsistDrag()
|
||||||
|
{
|
||||||
|
for (int i = _dragBlockStart; i >= 0 && i < _dragBlockStart + _dragBlockLength; i++)
|
||||||
|
if (i < consistStack.Children.Count)
|
||||||
|
consistStack.Children[i].IsVisible = true;
|
||||||
|
|
||||||
|
if (_carried != null)
|
||||||
|
consistOverlay.Children.Remove(_carried);
|
||||||
|
_carried = null;
|
||||||
|
|
||||||
|
_dropTracker.Reset();
|
||||||
|
_edgeScroller.Stop();
|
||||||
|
_dropGap.Hide(consistStack);
|
||||||
|
|
||||||
|
_dragBlockStart = -1;
|
||||||
|
_dragBlockLength = 0;
|
||||||
|
_pressIndex = -1;
|
||||||
|
_dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveConsistBlock(Trainset trainset, int start, int length, int target)
|
||||||
|
{
|
||||||
|
var vehicles = trainset.Vehicles;
|
||||||
|
if (start < 0 || length <= 0 || start + length > vehicles.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var block = vehicles.GetRange(start, length);
|
||||||
|
vehicles.RemoveRange(start, length);
|
||||||
|
|
||||||
|
target = Math.Clamp(target, 0, vehicles.Count);
|
||||||
|
if (target == start)
|
||||||
|
{
|
||||||
|
vehicles.InsertRange(start, block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicles.InsertRange(target, block);
|
||||||
|
|
||||||
|
RefreshVehicleLabels();
|
||||||
|
ShowConsist(trainset);
|
||||||
|
UpdateTrainStats(trainset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HoldsNext(Dynamic car) =>
|
||||||
|
car.Coupling.Has(Coupling.WorkshopLock) || car.Coupling.Locked;
|
||||||
|
|
||||||
|
private static int SetStart(List<Dynamic> vehicles, int index)
|
||||||
|
{
|
||||||
|
while (index > 0 && HoldsNext(vehicles[index - 1]))
|
||||||
|
index--;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int SetLength(List<Dynamic> vehicles, int start)
|
||||||
|
{
|
||||||
|
int length = 1;
|
||||||
|
while (start + length < vehicles.Count && HoldsNext(vehicles[start + length - 1]))
|
||||||
|
length++;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateTrainStats(Trainset? trainset)
|
private void UpdateTrainStats(Trainset? trainset)
|
||||||
{
|
{
|
||||||
if (trainset is null)
|
if (trainset is null)
|
||||||
{
|
{
|
||||||
trainStats.Text = "";
|
trainStats.Children.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var db = GameData.Instance.Vehicles;
|
var db = GameData.Instance.Vehicles;
|
||||||
trainStats.Text = TrainsetDisplay.FormatStats(
|
StarterNG.Infrastructure.StatsBar.Fill(trainStats, TrainsetDisplay.StatsFields(
|
||||||
trainset,
|
trainset,
|
||||||
car => Physics.For(car.DataFolder, db.TextureForSkin(car.SkinFile)?.Model)
|
car => Physics.For(car.DataFolder, db.TextureForSkin(car.SkinFile)?.Model)
|
||||||
?? Physics.For(car.DataFolder, car.MmdFile)
|
?? Physics.For(car.DataFolder, car.MmdFile)
|
||||||
?? Physics.For(car.DataFolder, car.SkinFile),
|
?? Physics.For(car.DataFolder, car.SkinFile),
|
||||||
App.Loc["Length"], App.Loc["Mass"], App.Loc["VehicleCount"], App.Loc["Track"],
|
car => db.TextureForSkin(car.SkinFile)?.ResolvedCategory));
|
||||||
car => db.TextureForSkin(car.SkinFile)?.ResolvedCategory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshSelectedConsist()
|
private void RefreshSelectedConsist()
|
||||||
|
|||||||
@@ -7,377 +7,551 @@
|
|||||||
x:Class="StarterNG.Views.Settings"
|
x:Class="StarterNG.Views.Settings"
|
||||||
x:CompileBindings="True">
|
x:CompileBindings="True">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<!-- Save / reset bar -->
|
|
||||||
<Border DockPanel.Dock="Bottom" Padding="16,10" Background="{StaticResource CarbonPanelBg}"
|
<Border DockPanel.Dock="Bottom" Padding="16,10" Background="{StaticResource CarbonPanelBg}"
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="0,1,0,0">
|
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="0,1,0,0">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Right">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<TextBlock x:Name="SaveStatus" VerticalAlignment="Center" Opacity="0.8" Text="" />
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||||
<Button x:Name="ResetButton" Classes="Flat" Click="ResetButton_OnClick"
|
<TextBlock VerticalAlignment="Center" Opacity="0.6" FontSize="11"
|
||||||
Content="{Binding [Reset], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding [SettingsPath], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<Button x:Name="SaveButton" Classes="Flat Accent" Click="SaveButton_OnClick"
|
<TextBlock x:Name="SettingsPathText" VerticalAlignment="Center" Opacity="0.8" FontSize="11"
|
||||||
Content="{Binding [Save], Source={x:Static starterNg:App.Loc}}" />
|
TextTrimming="CharacterEllipsis" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="12" HorizontalAlignment="Right">
|
||||||
|
<TextBlock x:Name="SaveStatus" VerticalAlignment="Center" Opacity="0.8" Text="" />
|
||||||
|
<Button x:Name="AboutButton" Classes="Flat" Click="AboutButton_OnClick"
|
||||||
|
Content="{Binding [About], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<Button x:Name="ResetButton" Classes="Flat" Click="ResetButton_OnClick"
|
||||||
|
Content="{Binding [Reset], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<Button x:Name="SaveButton" Classes="Flat Accent" Click="SaveButton_OnClick"
|
||||||
|
Content="{Binding [Save], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<TabControl TabStripPlacement="Left">
|
<TabControl TabStripPlacement="Top">
|
||||||
<!-- General -->
|
|
||||||
<TabItem Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
|
<TabItem Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
<Grid ColumnDefinitions="*,10,*,10,200" Margin="10">
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<Label Content="{Binding [Language], Source={x:Static starterNg:App.Loc}}" VerticalAlignment="Center" FontSize="14" />
|
|
||||||
<!-- Filled from the lang/ folder in the constructor. -->
|
|
||||||
<ComboBox x:Name="ChangeLanguageCb" MinWidth="350" MaxWidth="350"
|
|
||||||
SelectionChanged="LanguageComboBox_OnSelectionChanged" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<CheckBox x:Name="FullscreenCb" Content="{Binding [Fullscreen], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Grid.Column="0" Spacing="10">
|
||||||
<CheckBox x:Name="PauseInactiveCb" Content="{Binding [PauseSimInactive], Source={x:Static starterNg:App.Loc}}" />
|
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||||
<CheckBox x:Name="PauseStartCb" Content="{Binding [PauseSimStart], Source={x:Static starterNg:App.Loc}}" />
|
Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [Language], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="ChangeLanguageCb" HorizontalAlignment="Stretch"
|
||||||
|
SelectionChanged="LanguageComboBox_OnSelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
<CheckBox x:Name="FullscreenCb" Content="{Binding [Fullscreen], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [CursorSensitivity], Source={x:Static starterNg:App.Loc}}" />
|
<CheckBox x:Name="PauseInactiveCb" Content="{Binding [PauseSimInactive], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<Slider x:Name="CursorSensitivitySlider" Minimum="1" Maximum="5" TickFrequency="1" MinWidth="200" MaxWidth="200"
|
<CheckBox x:Name="PauseStartCb" Content="{Binding [PauseSimStart], Source={x:Static starterNg:App.Loc}}" />
|
||||||
IsSnapToTickEnabled="True" Value="3" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<CheckBox x:Name="MouseHorInvertCb" Content="{Binding [MouseHorInvert], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Classes="Field">
|
||||||
<CheckBox x:Name="MouseVertInvertCb" Content="{Binding [MouseVertInvert], Source={x:Static starterNg:App.Loc}}" />
|
<DockPanel>
|
||||||
</StackPanel>
|
<TextBlock x:Name="cursorSensitivityValue" DockPanel.Dock="Right" Classes="FieldValue" />
|
||||||
</ScrollViewer>
|
<TextBlock Classes="FieldLabel"
|
||||||
</TabItem>
|
Text="{Binding [CursorSensitivity], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="CursorSensitivitySlider" Minimum="1" Maximum="5" TickFrequency="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsSnapToTickEnabled="True" Value="3"
|
||||||
|
ValueChanged="QualitySlider_OnValueChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Communication -->
|
<CheckBox x:Name="MouseHorInvertCb" Content="{Binding [MouseHorInvert], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<TabItem Header="{Binding [Communication], Source={x:Static starterNg:App.Loc}}">
|
<CheckBox x:Name="MouseVertInvertCb" Content="{Binding [MouseVertInvert], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
|
||||||
<CheckBox x:Name="GamepadIgnoreCb" Content="{Binding [GamepadSignalIgnore], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Feedback], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox x:Name="FeedbackCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Tag="0" Content="{Binding [FeedbackOff], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="1" Content="{Binding [FeedbackCASHP], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="2" Content="{Binding [FeedbackCA], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="3" Content="{Binding [FeedbackLPT], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="4" Content="{Binding [FeedbackPoKeys], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Tag="5" Content="{Binding [FeedbackSP], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel x:Name="FeedbackPortRow" Spacing="8" Orientation="Horizontal" IsVisible="False">
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [FeedbackPort], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<NumericUpDown x:Name="FeedbackPortNud" Minimum="0" Maximum="65535" Value="888" Width="120" />
|
|
||||||
</StackPanel>
|
|
||||||
<Expander x:Name="UartExpander" IsVisible="False" Header="{Binding [UartSection], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
|
||||||
<CheckBox x:Name="UartEnableCb" Content="{Binding [UartEnable], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [UartPort], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<TextBox x:Name="UartPortTb" MinWidth="150" PlaceholderText="COM3" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
</HeaderedContentControl>
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [UartTune], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<TextBox x:Name="UartTuneTb" MinWidth="150" />
|
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [Communication], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<CheckBox x:Name="GamepadIgnoreCb" Content="{Binding [GamepadSignalIgnore], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [Feedback], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="FeedbackCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Tag="0" Content="{Binding [FeedbackOff], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="1" Content="{Binding [FeedbackCASHP], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="2" Content="{Binding [FeedbackCA], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="3" Content="{Binding [FeedbackLPT], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="4" Content="{Binding [FeedbackPoKeys], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="5" Content="{Binding [FeedbackSP], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel x:Name="FeedbackPortRow" Classes="Field" IsVisible="False">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [FeedbackPort], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<NumericUpDown x:Name="FeedbackPortNud" Minimum="0" Maximum="65535" Value="888"
|
||||||
|
Increment="1" FormatString="0"
|
||||||
|
HorizontalAlignment="Left" Width="140" />
|
||||||
|
</StackPanel>
|
||||||
|
<Button x:Name="ComButton" Classes="Flat" Cursor="Hand"
|
||||||
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
|
IsVisible="False" Click="ComButton_OnClick"
|
||||||
|
Content="{Binding [UartSection], Source={x:Static starterNg:App.Loc}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<CheckBox x:Name="UartDebugCb" Content="{Binding [UartDebug], Source={x:Static starterNg:App.Loc}}" />
|
</HeaderedContentControl>
|
||||||
<CheckBox x:Name="UartMainCb" Content="{Binding [UartMain], Source={x:Static starterNg:App.Loc}}" />
|
</StackPanel>
|
||||||
<CheckBox x:Name="UartScndCb" Content="{Binding [UartScnd], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="UartTrainCb" Content="{Binding [UartTrain], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Grid.Column="2" Spacing="10">
|
||||||
<CheckBox x:Name="UartLocalCb" Content="{Binding [UartLocal], Source={x:Static starterNg:App.Loc}}" />
|
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||||
<CheckBox x:Name="UartRadioVolumeCb" Content="{Binding [UartRadioVolume], Source={x:Static starterNg:App.Loc}}" />
|
Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
|
||||||
<CheckBox x:Name="UartRadioChannelCb" Content="{Binding [UartRadioChannel], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Spacing="8">
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="SelectExeCb" HorizontalAlignment="Stretch" SelectedIndex="0" />
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox x:Name="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="VirtualShuntingCb" Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="LogMissingVehicleFilesCb" Content="{Binding [LogMissingVehicleFiles], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
|
||||||
|
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<CheckBox x:Name="AutoCloseStarterCb" Content="{Binding [AutoCloseStarter], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="LargeThumbnailsCb" Content="{Binding [LargeThumbnails], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="AutoExpandTreeCb" Content="{Binding [AutoExpandSceneryTree], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [BatteryDefault], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="BatteryDefaultCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [BatteryDefaultScenario], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [BatteryDefaultOff], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [BatteryDefaultOn], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<HeaderedContentControl Grid.Column="4" VerticalAlignment="Top"
|
||||||
|
Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [SettingsProfile], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="6">
|
||||||
|
<ComboBox x:Name="ProfileCb" HorizontalAlignment="Stretch" />
|
||||||
|
<Button x:Name="ProfileApplyBtn" Classes="Flat" HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Content="{Binding [ProfileApply], Source={x:Static starterNg:App.Loc}}"
|
||||||
|
Click="ProfileApplyButton_OnClick" />
|
||||||
|
<Button x:Name="ProfileSaveAsBtn" Classes="Flat" HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
Content="{Binding [ProfileSaveAs], Source={x:Static starterNg:App.Loc}}"
|
||||||
|
Click="ProfileSaveAsButton_OnClick" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Expander>
|
</HeaderedContentControl>
|
||||||
</StackPanel>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<!-- Controls / key bindings -->
|
|
||||||
<TabItem Header="{Binding [Controls], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<Grid x:Name="ControlsHost" RowDefinitions="Auto,*,Auto" Margin="12">
|
|
||||||
<!-- Built in code-behind (BuildControlsTab): a search bar, the
|
|
||||||
scrollable binding list, and the keyboard visualisation. -->
|
|
||||||
</Grid>
|
|
||||||
</TabItem>
|
|
||||||
|
|
||||||
<!-- Others -->
|
|
||||||
<TabItem Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox x:Name="SelectExeCb" MinWidth="350" MaxWidth="350" SelectedIndex="0" />
|
|
||||||
</StackPanel>
|
|
||||||
<CheckBox x:Name="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="VirtualShuntingCb" Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="LogMissingVehicleFilesCb" Content="{Binding [LogMissingVehicleFiles], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
</TabItem>
|
|
||||||
|
|
||||||
<!-- Graphic -->
|
|
||||||
<TabItem Header="{Binding [Graphic], Source={x:Static starterNg:App.Loc}}">
|
<TabItem Header="{Binding [Graphic], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
<Grid Margin="10" ColumnDefinitions="250,10,250,10,250">
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [RenderEngine], Source={x:Static starterNg:App.Loc}}" />
|
<HeaderedContentControl Grid.Column="0" VerticalAlignment="Top"
|
||||||
<ComboBox x:Name="RenderEngineCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
Theme="{StaticResource GroupBox}"
|
||||||
<ComboBoxItem Content="{Binding [REFull], Source={x:Static starterNg:App.Loc}}" />
|
Header="{Binding [GfxColResolution], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ComboBoxItem Content="{Binding [REOld], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Spacing="8">
|
||||||
<ComboBoxItem Content="{Binding [RESimpShader], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Classes="Field">
|
||||||
<ComboBoxItem Content="{Binding [RESimp], Source={x:Static starterNg:App.Loc}}" />
|
<TextBlock Classes="FieldLabel"
|
||||||
<ComboBoxItem Content="{Binding [REOff], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding [Resolution], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<ComboBoxItem Content="{Binding [REExperimental], Source={x:Static starterNg:App.Loc}}" />
|
<ComboBox x:Name="ResolutionCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||||
</ComboBox>
|
<ComboBoxItem>3840x2160</ComboBoxItem>
|
||||||
|
<ComboBoxItem>2560x1440</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1920x1080</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1600x900</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1366x768</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1280x720</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox x:Name="FullscreenWindowedCb" Content="{Binding [FullscreenWindowed], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #bufferScale.Value, StringFormat='{}{0:0}%'}" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [BufforResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="bufferScale" Minimum="50" Maximum="200" TickFrequency="1" IsSnapToTickEnabled="True" Value="100" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Name="texResolution" Text="4096 px" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [MaxTexResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="textureResolutionSlider" Minimum="9" Maximum="14" TickFrequency="1" IsSnapToTickEnabled="True" Value="12" ValueChanged="TextureResolutionSlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Name="cabTexResolution" Text="4096 px" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [MaxTexCabResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="cabTextureResolutionSlider" Minimum="9" Maximum="14" TickFrequency="1" IsSnapToTickEnabled="True" Value="12" ValueChanged="CabTextureResolutionSlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="texFilteringValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [TexFilteringQuality], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="TexFilteringSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="5" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="multisamplingValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [Multisampling], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="MultisamplingSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="3" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="trackCurvesValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [TrackCurvesQuality], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="TrackCurvesSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="renderRangeValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [RenderRange], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="RenderRangeSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="3" TickFrequency="1" IsSnapToTickEnabled="True" Value="1" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
|
||||||
|
<HeaderedContentControl Grid.Column="2" VerticalAlignment="Top"
|
||||||
|
Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [GfxColRendering], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [RenderEngine], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="RenderEngineCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [REFull], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [REOld], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [RESimpShader], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [RESimp], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [REOff], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [REExperimental], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox x:Name="RenderAngleVulkanCb" Content="{Binding [RenderAngleVulkan], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="AdditionalShadersCb" Content="{Binding [AdditionalShadersEffects], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="ChromaticAberrationCb" Content="{Binding [ChromaticAberration], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="MotionBlurCb" Content="{Binding [MotionBlur], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="SkipPipelineCb" Content="SkipPipeline" />
|
||||||
|
<CheckBox x:Name="RenderVBOCb" Content="{Binding [RenderVBO], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="VSyncCb" Content="{Binding [VSync], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #FpsLimitSlider.Value, StringFormat='{}{0:0} FPS'}" />
|
||||||
|
<CheckBox x:Name="FpsLimitEnableCb" Content="{Binding [FpsLimitEnable], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="FpsLimitSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="100" TickFrequency="1" IsSnapToTickEnabled="True" Value="30"
|
||||||
|
IsEnabled="{Binding #FpsLimitEnableCb.IsChecked}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
<CheckBox x:Name="SmokeDisplayCb" Content="{Binding [SmokeDisplay], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<StackPanel Classes="Field" IsEnabled="{Binding #SmokeDisplayCb.IsChecked}">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="smokeParticlesValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [SmokeParticlesMultiplier], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="SmokeParticlesSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="2" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #DynamicLightsSlider.Value, StringFormat='{}{0:0}'}" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [DynamicLights], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="DynamicLightsSlider" Minimum="0" Maximum="7" TickFrequency="1" IsSnapToTickEnabled="True" Value="7" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #fovSlider.Value, StringFormat='{}{0:0}°'}" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [FieldOfView], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="fovSlider" Minimum="5" Maximum="110" TickFrequency="5" IsSnapToTickEnabled="True" Value="30" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="4" Spacing="10">
|
||||||
|
<HeaderedContentControl VerticalAlignment="Top"
|
||||||
|
Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [GfxColShadows], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<HeaderedContentControl.Tag>
|
||||||
|
<CheckBox x:Name="RenderShadowsCb" FontSize="11" MinHeight="0"
|
||||||
|
Content="{Binding [RenderShadows], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</HeaderedContentControl.Tag>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" IsEnabled="{Binding #RenderShadowsCb.IsChecked}">
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [ShadowDisplay], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="ShadowDisplayCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [CSROnlyImportant], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [CSRLimited], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [CSRAll], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="shaderResolution" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [ShadowsResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="shaderResolutionSlider" Minimum="9" Maximum="14" TickFrequency="1" IsSnapToTickEnabled="True" Value="12" ValueChanged="shaderResolutionSlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="shaderRangeValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [ShaderRange], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="shaderRange" Minimum="25" Maximum="400" TickFrequency="5" IsSnapToTickEnabled="True" Value="150"
|
||||||
|
ValueChanged="QualitySlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="cabShadowRangeValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [CabShadowsRange], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="cabShaderSourceRange" Minimum="0" Maximum="100" TickFrequency="10" IsSnapToTickEnabled="True" Value="30"
|
||||||
|
ValueChanged="QualitySlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #ShadowAngleLimitSlider.Value, StringFormat='{}{0:0.0}'}" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [ShadowAngleLimit], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="ShadowAngleLimitSlider" Minimum="0.2" Maximum="1.0" TickFrequency="0.1" Value="0.2" HorizontalAlignment="Stretch" />
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldHint"
|
||||||
|
Text="{Binding [ShadowLimitShort], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<TextBlock Classes="FieldHint"
|
||||||
|
Text="{Binding [ShadowLimitNone], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
|
||||||
|
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [GfxColReflections], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<CheckBox x:Name="ReflectionsCubeMapCb" Content="{Binding [ReflectionsCubeMap], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [ReflectionsDetails], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="ReflectionsDetailsCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [RDTerrain], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [RDTerrainModels], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Content="{Binding [RDTerrainModelsVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="reflectionsFramerateValue" />
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [ReflectionsFramerate], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider Name="reflectionsFramerate" Minimum="5" Maximum="120" TickFrequency="5" IsSnapToTickEnabled="True" Value="30"
|
||||||
|
ValueChanged="QualitySlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
|
||||||
|
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [GfxColScripts], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<CheckBox x:Name="RenderScreensCb" Content="{Binding [RenderScreens], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="RenderScreensThreadCb" Content="{Binding [RenderScreensThread], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<TextBlock Classes="FieldLabel"
|
||||||
|
Text="{Binding [PyScreenPriority], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBox x:Name="PyScreenPriorityCb" HorizontalAlignment="Stretch">
|
||||||
|
<ComboBoxItem Tag="normal" Content="{Binding [PyPriorityNormal], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="lower" Content="{Binding [PyPriorityLower], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="lowest" Content="{Binding [PyPriorityLowest], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="idle" Content="{Binding [PyPriorityIdle], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<ComboBoxItem Tag="off" Content="{Binding [PyPriorityOff], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
</Grid>
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Resolution], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox x:Name="ResolutionCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
|
||||||
<ComboBoxItem>3840x2160</ComboBoxItem>
|
|
||||||
<ComboBoxItem>2560x1440</ComboBoxItem>
|
|
||||||
<ComboBoxItem>1920x1080</ComboBoxItem>
|
|
||||||
<ComboBoxItem>1600x900</ComboBoxItem>
|
|
||||||
<ComboBoxItem>1366x768</ComboBoxItem>
|
|
||||||
<ComboBoxItem>1280x720</ComboBoxItem>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BufforResolution], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="bufferScale" Minimum="50" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="200" TickFrequency="1" IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{Binding #bufferScale.Value, StringFormat='{}{0}%'}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexResolution], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="textureResolutionSlider" Minimum="9" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="14" ValueChanged="TextureResolutionSlider_OnValueChanged"
|
|
||||||
TickFrequency="1" IsSnapToTickEnabled="True" Value="12" />
|
|
||||||
<TextBlock Name="texResolution" VerticalAlignment="Center" Text="4096" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexCabResolution], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="cabTextureResolutionSlider" Minimum="9" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="14" ValueChanged="CabTextureResolutionSlider_OnValueChanged"
|
|
||||||
TickFrequency="1" IsSnapToTickEnabled="True" Value="12" />
|
|
||||||
<TextBlock Name="cabTexResolution" VerticalAlignment="Center" Text="4096" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TexFilteringQuality], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="TexFilteringSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="5" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Multisampling], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="MultisamplingSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="3" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [DynamicLights], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="DynamicLightsSlider" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="7" TickFrequency="1" IsSnapToTickEnabled="True" Value="7" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderRange], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="RenderRangeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="3" Value="1" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrackCurvesQuality], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="TrackCurvesSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<CheckBox x:Name="FpsLimitEnableCb" Content="{Binding [FpsLimitEnable], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="FpsLimitSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100" TickFrequency="1" IsSnapToTickEnabled="True" Value="30" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowAngleLimit], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="ShadowAngleLimitSlider" Minimum="0.2" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="1.0" TickFrequency="0.1" Value="0.2" />
|
|
||||||
</StackPanel>
|
|
||||||
<CheckBox x:Name="FullscreenWindowedCb" Content="{Binding [FullscreenWindowed], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="RenderAngleVulkanCb" Content="{Binding [RenderAngleVulkan], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="VSyncCb" Content="{Binding [VSync], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="SmokeDisplayCb" Content="{Binding [SmokeDisplay], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [SmokeParticlesMultiplier], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="SmokeParticlesSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="2" />
|
|
||||||
</StackPanel>
|
|
||||||
<CheckBox x:Name="ChromaticAberrationCb" Content="{Binding [ChromaticAberration], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="MotionBlurCb" Content="{Binding [MotionBlur], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="AdditionalShadersCb" Content="{Binding [AdditionalShadersEffects], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="ReflectionsCubeMapCb" Content="{Binding [ReflectionsCubeMap], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="RenderVBOCb" Content="{Binding [RenderVBO], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="RenderShadowsCb" Content="{Binding [RenderShadows], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsFramerate], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="reflectionsFramerate" Minimum="5" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="120" TickFrequency="5" IsSnapToTickEnabled="True" Value="30" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #reflectionsFramerate.Value, StringFormat='{}{0} FPS'}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowsResolution], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="shaderResolutionSlider" Minimum="9" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="14" ValueChanged="shaderResolutionSlider_OnValueChanged"
|
|
||||||
TickFrequency="1" IsSnapToTickEnabled="True" Value="12" />
|
|
||||||
<TextBlock Name="shaderResolution" VerticalAlignment="Center" Text="4096 px" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShaderRange], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="shaderRange" Minimum="25" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="400" TickFrequency="5" IsSnapToTickEnabled="True" Value="150" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #shaderRange.Value, StringFormat='{}{0} m'}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [CabShadowsRange], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="cabShaderSourceRange" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100" TickFrequency="10" IsSnapToTickEnabled="True" Value="30" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #cabShaderSourceRange.Value, StringFormat='{}{0} m'}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowDisplay], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox x:Name="ShadowDisplayCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Content="{Binding [CSROnlyImportant], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Content="{Binding [CSRLimited], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Content="{Binding [CSRAll], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsDetails], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox x:Name="ReflectionsDetailsCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Content="{Binding [RDTerrain], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Content="{Binding [RDTerrainModels], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Content="{Binding [RDTerrainModelsVehicles], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [FieldOfView], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider Name="fovSlider" Minimum="5" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="110" TickFrequency="5" IsSnapToTickEnabled="True" Value="30" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #fovSlider.Value, StringFormat='{}{0}°'}" />
|
|
||||||
</StackPanel>
|
|
||||||
<CheckBox x:Name="RenderScreensCb" Content="{Binding [RenderScreens], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="RenderScreensThreadCb" Content="{Binding [RenderScreensThread], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<!-- Physics -->
|
<TabItem Header="{Binding [SoundPhysics], Source={x:Static starterNg:App.Loc}}">
|
||||||
<TabItem Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<Grid ColumnDefinitions="360,10,360" Margin="10">
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
|
||||||
<CheckBox x:Name="PhysicsAccuracyCb" Content="{Binding [PhysicsAccuracyIncreased], Source={x:Static starterNg:App.Loc}}" />
|
<HeaderedContentControl Grid.Column="0" VerticalAlignment="Top"
|
||||||
<CheckBox x:Name="PantographBreakCb" Content="{Binding [BreakingPantographPossibility], Source={x:Static starterNg:App.Loc}}" />
|
Theme="{StaticResource GroupBox}"
|
||||||
<CheckBox x:Name="OverheadOnlyCb" Content="{Binding [PowerOverheadLineOnly], Source={x:Static starterNg:App.Loc}}" />
|
Header="{Binding [Sounds], Source={x:Static starterNg:App.Loc}}">
|
||||||
<CheckBox x:Name="SpeedometerTapesCb" Content="{Binding [SaveSpeedometerTapes], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Spacing="8">
|
||||||
<CheckBox x:Name="SimLogsCb" Content="{Binding [SaveSimLogs], Source={x:Static starterNg:App.Loc}}" />
|
<CheckBox x:Name="EnableSoundsCb" Content="{Binding [EnableSounds], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<CheckBox x:Name="KeepLogsCb" Content="{Binding [KeepPrevLogs], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="DisplaySimulationCb" Content="{Binding [DisplaySimulation], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Classes="Field">
|
||||||
<CheckBox x:Name="CrashDamageCb" Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}" />
|
<DockPanel>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Friction], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding #VolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||||
<Slider x:Name="FrictionSlider" Minimum="0.1" MaxWidth="400" HorizontalAlignment="Left"
|
<TextBlock Classes="FieldLabel" Text="{Binding [Volume], Source={x:Static starterNg:App.Loc}}" />
|
||||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
</DockPanel>
|
||||||
</StackPanel>
|
<Slider x:Name="VolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BrakeStep], Source={x:Static starterNg:App.Loc}}" />
|
</StackPanel>
|
||||||
<Slider x:Name="BrakeStepSlider" Minimum="0.4" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
<StackPanel Classes="Field">
|
||||||
</StackPanel>
|
<DockPanel>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BrakeSpeed], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding #RadioVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||||
<Slider x:Name="BrakeSpeedSlider" Minimum="0.4" MaxWidth="400" HorizontalAlignment="Left"
|
<TextBlock Classes="FieldLabel" Text="{Binding [RadiophoneVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
</DockPanel>
|
||||||
</StackPanel>
|
<Slider x:Name="RadioVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||||
</StackPanel>
|
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #VehiclesVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||||
|
<TextBlock Classes="FieldLabel" Text="{Binding [VehiclesVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="VehiclesVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #PositionalVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||||
|
<TextBlock Classes="FieldLabel" Text="{Binding [PositionedSoundsVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="PositionalVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #AmbientVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||||
|
<TextBlock Classes="FieldLabel" Text="{Binding [AmbientVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="AmbientVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #VolumePausedSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||||
|
<TextBlock Classes="FieldLabel" Text="{Binding [VolumePaused], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="VolumePausedSlider" Minimum="0" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
|
||||||
|
<HeaderedContentControl Grid.Column="2" VerticalAlignment="Top"
|
||||||
|
Theme="{StaticResource GroupBox}"
|
||||||
|
Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<CheckBox x:Name="PhysicsAccuracyCb" Content="{Binding [PhysicsAccuracyIncreased], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="PantographBreakCb" Content="{Binding [BreakingPantographPossibility], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="OverheadOnlyCb" Content="{Binding [PowerOverheadLineOnly], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="SpeedometerTapesCb" Content="{Binding [SaveSpeedometerTapes], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="SimLogsCb" Content="{Binding [SaveSimLogs], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="KeepLogsCb" Content="{Binding [KeepPrevLogs], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="DisplaySimulationCb" Content="{Binding [DisplaySimulation], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="DebugLogVisibleCb" Content="{Binding [DebugLogVisible], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<CheckBox x:Name="CrashDamageCb" Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #FrictionSlider.Value, StringFormat='{}{0:0.00}'}" />
|
||||||
|
<TextBlock Classes="FieldLabel" Text="{Binding [Friction], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="FrictionSlider" Minimum="0.1" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #BrakeStepSlider.Value, StringFormat='{}{0:0.00}'}" />
|
||||||
|
<TextBlock Classes="FieldLabel" Text="{Binding [BrakeStep], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="BrakeStepSlider" Minimum="0.4" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Classes="Field">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||||
|
Text="{Binding #BrakeSpeedSlider.Value, StringFormat='{}{0:0.00}'}" />
|
||||||
|
<TextBlock Classes="FieldLabel" Text="{Binding [BrakeSpeed], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Slider x:Name="BrakeSpeedSlider" Minimum="0.4" HorizontalAlignment="Stretch"
|
||||||
|
Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</HeaderedContentControl>
|
||||||
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<!-- Sounds -->
|
<TabItem Header="{Binding [Controls], Source={x:Static starterNg:App.Loc}}">
|
||||||
<TabItem Header="{Binding [Sounds], Source={x:Static starterNg:App.Loc}}">
|
<Grid x:Name="ControlsHost" RowDefinitions="Auto,*,Auto" Margin="10">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
</Grid>
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
</TabItem>
|
||||||
<CheckBox x:Name="EnableSoundsCb" Content="{Binding [EnableSounds], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Volume], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="VolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RadiophoneVolume], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="RadioVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VehiclesVolume], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="VehiclesVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [PositionedSoundsVolume], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="PositionalVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [AmbientVolume], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="AmbientVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
</TabItem>
|
|
||||||
|
|
||||||
<!-- Advanced -->
|
|
||||||
<TabItem Header="{Binding [Advanced], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
|
||||||
<CheckBox x:Name="CompressTexturesCb" Content="{Binding [CompressTextures], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="ScaleSpecularsCb" Content="{Binding [ScaleSpeculars], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="UseGLESCb" Content="{Binding [UseGLES], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="ShaderGammaCb" Content="{Binding [ShaderGamma], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="ScreenMipmapsCb" Content="{Binding [ScreenMipmaps], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="ExtendedModelConversionCb" Content="{Binding [ExtendedModelConversion], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="GfxResourceMoveCb" Content="{Binding [GfxResourceMove], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="GfxResourceSweepCb" Content="{Binding [GfxResourceSweep], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrainPhysicsThreads], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<Slider x:Name="TrainPhysicsThreadsSlider" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="0" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #TrainPhysicsThreadsSlider.Value}" />
|
|
||||||
</StackPanel>
|
|
||||||
<CheckBox x:Name="IgnoreIrrelevantTrainsCb" Content="{Binding [IgnoreIrrelevantTrains], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
</TabItem>
|
|
||||||
|
|
||||||
<!-- Starter -->
|
|
||||||
<TabItem Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{Binding [SettingsProfile], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox x:Name="ProfileCb" MinWidth="220" />
|
|
||||||
<Button x:Name="ProfileApplyBtn" Content="{Binding [ProfileApply], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
Click="ProfileApplyButton_OnClick" />
|
|
||||||
<Button x:Name="ProfileSaveAsBtn" Content="{Binding [ProfileSaveAs], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
Click="ProfileSaveAsButton_OnClick" />
|
|
||||||
</StackPanel>
|
|
||||||
<CheckBox x:Name="AutoCloseStarterCb" Content="{Binding [AutoCloseStarter], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="LargeThumbnailsCb" Content="{Binding [LargeThumbnails], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="AutoExpandTreeCb" Content="{Binding [AutoExpandSceneryTree], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<CheckBox x:Name="HideArchivalVehiclesCb" Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{Binding [BatteryDefault], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBox x:Name="BatteryDefaultCb" MinWidth="220" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Content="{Binding [BatteryDefaultScenario], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Content="{Binding [BatteryDefaultOff], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<ComboBoxItem Content="{Binding [BatteryDefaultOn], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
</TabItem>
|
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -26,18 +26,22 @@ public partial class Settings : UserControl
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
TrainPhysicsThreadsSlider.Maximum = Environment.ProcessorCount;
|
SettingsPathText.Text = StarterNG.Classes.Settings.Instance.LoadedFrom;
|
||||||
|
ToolTip.SetTip(SettingsPathText, StarterNG.Classes.Settings.Instance.LoadedFrom);
|
||||||
|
|
||||||
this.AttachedToVisualTree += (_, _) =>
|
this.AttachedToVisualTree += (_, _) =>
|
||||||
{
|
{
|
||||||
TextureResolutionSlider_OnValueChanged(null, null);
|
TextureResolutionSlider_OnValueChanged(null, null);
|
||||||
CabTextureResolutionSlider_OnValueChanged(null, null);
|
CabTextureResolutionSlider_OnValueChanged(null, null);
|
||||||
shaderResolutionSlider_OnValueChanged(null, null);
|
shaderResolutionSlider_OnValueChanged(null, null);
|
||||||
|
UpdateQualityCaptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
FillLanguageCombo();
|
FillLanguageCombo();
|
||||||
|
|
||||||
App.Loc.LanguageChanged += SelectActiveLanguage;
|
App.Loc.LanguageChanged += SelectActiveLanguage;
|
||||||
|
App.Loc.LanguageChanged += UpdateQualityCaptions;
|
||||||
|
App.Loc.LanguageChanged += () => shaderResolutionSlider_OnValueChanged(null, null);
|
||||||
|
|
||||||
FeedbackCb.SelectionChanged += FeedbackCb_OnSelectionChanged;
|
FeedbackCb.SelectionChanged += FeedbackCb_OnSelectionChanged;
|
||||||
FpsLimitEnableCb.IsCheckedChanged += (_, _) => FpsLimitSlider.IsEnabled = IsChecked(FpsLimitEnableCb);
|
FpsLimitEnableCb.IsCheckedChanged += (_, _) => FpsLimitSlider.IsEnabled = IsChecked(FpsLimitEnableCb);
|
||||||
@@ -52,6 +56,15 @@ public partial class Settings : UserControl
|
|||||||
AddHandler(KeyDownEvent, OnControlsKeyDown, RoutingStrategies.Tunnel);
|
AddHandler(KeyDownEvent, OnControlsKeyDown, RoutingStrategies.Tunnel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int IndexOfTag(ComboBox cb, string? tag)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < cb.Items.Count; i++)
|
||||||
|
if (cb.Items[i] is ComboBoxItem item &&
|
||||||
|
string.Equals(item.Tag as string, tag, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
private static int FindComboIndexByContent(ComboBox cb, string content)
|
private static int FindComboIndexByContent(ComboBox cb, string content)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < cb.Items.Count; i++)
|
for (int i = 0; i < cb.Items.Count; i++)
|
||||||
@@ -146,16 +159,6 @@ public partial class Settings : UserControl
|
|||||||
GamepadIgnoreCb.IsChecked = s.IgnoreGamepad;
|
GamepadIgnoreCb.IsChecked = s.IgnoreGamepad;
|
||||||
FeedbackCb.SelectedIndex = s.FeedbackMode;
|
FeedbackCb.SelectedIndex = s.FeedbackMode;
|
||||||
FeedbackPortNud.Value = (decimal)s.FeedbackPort;
|
FeedbackPortNud.Value = (decimal)s.FeedbackPort;
|
||||||
UartEnableCb.IsChecked = s.UartEnabled;
|
|
||||||
UartPortTb.Text = s.UartPort;
|
|
||||||
UartTuneTb.Text = s.UartTune;
|
|
||||||
UartDebugCb.IsChecked = s.UartDebug;
|
|
||||||
UartMainCb.IsChecked = s.UartMain;
|
|
||||||
UartScndCb.IsChecked = s.UartScnd;
|
|
||||||
UartTrainCb.IsChecked = s.UartTrain;
|
|
||||||
UartLocalCb.IsChecked = s.UartLocal;
|
|
||||||
UartRadioVolumeCb.IsChecked = s.UartRadioVolume;
|
|
||||||
UartRadioChannelCb.IsChecked = s.UartRadioChannel;
|
|
||||||
UpdateFeedbackDetailVisibility();
|
UpdateFeedbackDetailVisibility();
|
||||||
|
|
||||||
if (s.SelectExeAutomatically)
|
if (s.SelectExeAutomatically)
|
||||||
@@ -229,28 +232,22 @@ public partial class Settings : UserControl
|
|||||||
VehiclesVolumeSlider.Value = s.VehiclesVolume;
|
VehiclesVolumeSlider.Value = s.VehiclesVolume;
|
||||||
PositionalVolumeSlider.Value = s.PositionalVolume;
|
PositionalVolumeSlider.Value = s.PositionalVolume;
|
||||||
AmbientVolumeSlider.Value = s.AmbientVolume;
|
AmbientVolumeSlider.Value = s.AmbientVolume;
|
||||||
|
VolumePausedSlider.Value = s.VolumePaused;
|
||||||
CompressTexturesCb.IsChecked = s.CompressTextures;
|
SkipPipelineCb.IsChecked = s.SkipPipeline;
|
||||||
ScaleSpecularsCb.IsChecked = s.ScaleSpeculars;
|
DebugLogVisibleCb.IsChecked = s.DebugLogVisible;
|
||||||
UseGLESCb.IsChecked = s.UseGLES;
|
PyScreenPriorityCb.SelectedIndex = Math.Max(0, IndexOfTag(PyScreenPriorityCb, s.PyScreenPriority));
|
||||||
ShaderGammaCb.IsChecked = s.ShaderGamma;
|
|
||||||
ScreenMipmapsCb.IsChecked = s.ScreenMipmaps;
|
|
||||||
ExtendedModelConversionCb.IsChecked = s.ExtendedModelConversion;
|
|
||||||
GfxResourceMoveCb.IsChecked = s.GfxResourceMove;
|
|
||||||
GfxResourceSweepCb.IsChecked = s.GfxResourceSweep;
|
|
||||||
TrainPhysicsThreadsSlider.Value = s.TrainPhysicsThreads;
|
|
||||||
IgnoreIrrelevantTrainsCb.IsChecked = s.IgnoreIrrelevantTrains;
|
|
||||||
|
|
||||||
AutoCloseStarterCb.IsChecked = s.AutoCloseStarter;
|
AutoCloseStarterCb.IsChecked = s.AutoCloseStarter;
|
||||||
LargeThumbnailsCb.IsChecked = s.LargeThumbnails;
|
LargeThumbnailsCb.IsChecked = s.LargeThumbnails;
|
||||||
AutoExpandTreeCb.IsChecked = s.AutoExpandSceneryTree;
|
AutoExpandTreeCb.IsChecked = s.AutoExpandSceneryTree;
|
||||||
HideArchivalVehiclesCb.IsChecked = s.HideArchivalVehicles;
|
|
||||||
BatteryDefaultCb.SelectedIndex = (int)s.BatteryDefault;
|
BatteryDefaultCb.SelectedIndex = (int)s.BatteryDefault;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateQualityCaptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadFromUi()
|
private void ReadFromUi()
|
||||||
@@ -269,16 +266,6 @@ public partial class Settings : UserControl
|
|||||||
s.IgnoreGamepad = IsChecked(GamepadIgnoreCb);
|
s.IgnoreGamepad = IsChecked(GamepadIgnoreCb);
|
||||||
s.FeedbackMode = Math.Max(0, FeedbackCb.SelectedIndex);
|
s.FeedbackMode = Math.Max(0, FeedbackCb.SelectedIndex);
|
||||||
s.FeedbackPort = (int)(FeedbackPortNud.Value ?? 888);
|
s.FeedbackPort = (int)(FeedbackPortNud.Value ?? 888);
|
||||||
s.UartEnabled = IsChecked(UartEnableCb);
|
|
||||||
s.UartPort = UartPortTb.Text ?? "";
|
|
||||||
s.UartTune = UartTuneTb.Text ?? "";
|
|
||||||
s.UartDebug = IsChecked(UartDebugCb);
|
|
||||||
s.UartMain = IsChecked(UartMainCb);
|
|
||||||
s.UartScnd = IsChecked(UartScndCb);
|
|
||||||
s.UartTrain = IsChecked(UartTrainCb);
|
|
||||||
s.UartLocal = IsChecked(UartLocalCb);
|
|
||||||
s.UartRadioVolume = IsChecked(UartRadioVolumeCb);
|
|
||||||
s.UartRadioChannel = IsChecked(UartRadioChannelCb);
|
|
||||||
|
|
||||||
s.SelectExeAutomatically = SelectExeCb.SelectedIndex == 0;
|
s.SelectExeAutomatically = SelectExeCb.SelectedIndex == 0;
|
||||||
s.ExecutablePath = s.SelectExeAutomatically ? "eu07.exe"
|
s.ExecutablePath = s.SelectExeAutomatically ? "eu07.exe"
|
||||||
@@ -340,25 +327,29 @@ public partial class Settings : UserControl
|
|||||||
s.VehiclesVolume = (int)VehiclesVolumeSlider.Value;
|
s.VehiclesVolume = (int)VehiclesVolumeSlider.Value;
|
||||||
s.PositionalVolume = (int)PositionalVolumeSlider.Value;
|
s.PositionalVolume = (int)PositionalVolumeSlider.Value;
|
||||||
s.AmbientVolume = (int)AmbientVolumeSlider.Value;
|
s.AmbientVolume = (int)AmbientVolumeSlider.Value;
|
||||||
|
s.VolumePaused = (int)VolumePausedSlider.Value;
|
||||||
s.CompressTextures = IsChecked(CompressTexturesCb);
|
s.SkipPipeline = IsChecked(SkipPipelineCb);
|
||||||
s.ScaleSpeculars = IsChecked(ScaleSpecularsCb);
|
s.DebugLogVisible = IsChecked(DebugLogVisibleCb);
|
||||||
s.UseGLES = IsChecked(UseGLESCb);
|
s.PyScreenPriority = (PyScreenPriorityCb.SelectedItem as ComboBoxItem)?.Tag as string ?? "normal";
|
||||||
s.ShaderGamma = IsChecked(ShaderGammaCb);
|
|
||||||
s.ScreenMipmaps = IsChecked(ScreenMipmapsCb);
|
|
||||||
s.ExtendedModelConversion = IsChecked(ExtendedModelConversionCb);
|
|
||||||
s.GfxResourceMove = IsChecked(GfxResourceMoveCb);
|
|
||||||
s.GfxResourceSweep = IsChecked(GfxResourceSweepCb);
|
|
||||||
s.TrainPhysicsThreads = (int)TrainPhysicsThreadsSlider.Value;
|
|
||||||
s.IgnoreIrrelevantTrains = IsChecked(IgnoreIrrelevantTrainsCb);
|
|
||||||
|
|
||||||
s.AutoCloseStarter = IsChecked(AutoCloseStarterCb);
|
s.AutoCloseStarter = IsChecked(AutoCloseStarterCb);
|
||||||
s.LargeThumbnails = IsChecked(LargeThumbnailsCb);
|
s.LargeThumbnails = IsChecked(LargeThumbnailsCb);
|
||||||
s.AutoExpandSceneryTree = IsChecked(AutoExpandTreeCb);
|
s.AutoExpandSceneryTree = IsChecked(AutoExpandTreeCb);
|
||||||
s.HideArchivalVehicles = IsChecked(HideArchivalVehiclesCb);
|
|
||||||
s.BatteryDefault = (BatteryDefault)Math.Max(0, BatteryDefaultCb.SelectedIndex);
|
s.BatteryDefault = (BatteryDefault)Math.Max(0, BatteryDefaultCb.SelectedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ComButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (TopLevel.GetTopLevel(this) is Window owner)
|
||||||
|
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)
|
private void SaveButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ReadFromUi();
|
ReadFromUi();
|
||||||
@@ -436,6 +427,7 @@ public partial class Settings : UserControl
|
|||||||
|
|
||||||
int resolution = 1 << (int)shaderResolutionSlider.Value;
|
int resolution = 1 << (int)shaderResolutionSlider.Value;
|
||||||
shaderResolution.Text = $"{resolution} px";
|
shaderResolution.Text = $"{resolution} px";
|
||||||
|
ToolTip.SetTip(shaderResolutionSlider, ShadowMapWord(resolution));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FillLanguageCombo()
|
private void FillLanguageCombo()
|
||||||
@@ -475,7 +467,7 @@ public partial class Settings : UserControl
|
|||||||
private void UpdateFeedbackDetailVisibility()
|
private void UpdateFeedbackDetailVisibility()
|
||||||
{
|
{
|
||||||
FeedbackPortRow.IsVisible = FeedbackCb.SelectedIndex == 3;
|
FeedbackPortRow.IsVisible = FeedbackCb.SelectedIndex == 3;
|
||||||
UartExpander.IsVisible = FeedbackCb.SelectedIndex == 5;
|
ComButton.IsVisible = FeedbackCb.SelectedIndex == 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsChecked(CheckBox cb) => cb.IsChecked == true;
|
private static bool IsChecked(CheckBox cb) => cb.IsChecked == true;
|
||||||
@@ -535,7 +527,7 @@ public partial class Settings : UserControl
|
|||||||
VerticalAlignment = VerticalAlignment.Center,
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
Foreground = FgDimBrush
|
Foreground = FgDimBrush
|
||||||
});
|
});
|
||||||
_controlsSearch = new TextBox { Width = 240, Watermark = App.Loc["Search"] };
|
_controlsSearch = new TextBox { Width = 240, PlaceholderText = App.Loc["Search"] };
|
||||||
_controlsSearch.TextChanged += (_, _) => RebuildBindingList();
|
_controlsSearch.TextChanged += (_, _) => RebuildBindingList();
|
||||||
topBar.Children.Add(_controlsSearch);
|
topBar.Children.Add(_controlsSearch);
|
||||||
|
|
||||||
@@ -1068,4 +1060,103 @@ public partial class Settings : UserControl
|
|||||||
|
|
||||||
private static string Capitalize(string s) =>
|
private static string Capitalize(string s) =>
|
||||||
string.IsNullOrEmpty(s) ? s : char.ToUpperInvariant(s[0]) + s.Substring(1);
|
string.IsNullOrEmpty(s) ? s : char.ToUpperInvariant(s[0]) + s.Substring(1);
|
||||||
|
|
||||||
|
private static string[] QualityWords => new[]
|
||||||
|
{
|
||||||
|
App.Loc["QualityVeryLow"], App.Loc["QualityLow"], App.Loc["QualityNormal"],
|
||||||
|
App.Loc["QualityHigh"], App.Loc["QualityVeryHigh"]
|
||||||
|
};
|
||||||
|
|
||||||
|
private void QualitySlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
||||||
|
=> UpdateQualityCaptions();
|
||||||
|
|
||||||
|
private void UpdateQualityCaptions()
|
||||||
|
{
|
||||||
|
var q = QualityWords;
|
||||||
|
|
||||||
|
if (texFilteringValue != null)
|
||||||
|
texFilteringValue.Text = 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];
|
||||||
|
|
||||||
|
string[] msaa = { App.Loc["MsaaNone"], "x2", "x4", "x8" };
|
||||||
|
if (multisamplingValue != null)
|
||||||
|
multisamplingValue.Text = 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 (cursorSensitivityValue != null)
|
||||||
|
cursorSensitivityValue.Text = (int)CursorSensitivitySlider.Value switch
|
||||||
|
{
|
||||||
|
<= 1 => q[1],
|
||||||
|
2 => App.Loc["QualityStandard"],
|
||||||
|
3 => q[3],
|
||||||
|
_ => q[4]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shaderRangeValue != null)
|
||||||
|
{
|
||||||
|
int metres = (int)shaderRange.Value;
|
||||||
|
shaderRangeValue.Text = $"{metres} m";
|
||||||
|
ToolTip.SetTip(shaderRange, ShadowRangeWord(metres));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cabShadowRangeValue != null)
|
||||||
|
{
|
||||||
|
int metres = (int)cabShaderSourceRange.Value;
|
||||||
|
cabShadowRangeValue.Text = metres <= 0 ? App.Loc["RangeDisabled"] : $"{metres} m";
|
||||||
|
ToolTip.SetTip(cabShaderSourceRange, CabShadowRangeWord(metres));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reflectionsFramerateValue != null)
|
||||||
|
{
|
||||||
|
int fps = (int)reflectionsFramerate.Value;
|
||||||
|
string word = ReflectionsRefreshWord(fps);
|
||||||
|
reflectionsFramerateValue.Text = $"{fps} FPS";
|
||||||
|
ToolTip.SetTip(reflectionsFramerate, word.Length == 0 ? null : word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ShadowMapWord(int pixels) => pixels switch
|
||||||
|
{
|
||||||
|
<= 512 => App.Loc["QualityVeryLow"],
|
||||||
|
<= 1024 => App.Loc["QualityLow"],
|
||||||
|
<= 2048 => App.Loc["QualityModerate"],
|
||||||
|
<= 4096 => App.Loc["QualityHigh"],
|
||||||
|
_ => App.Loc["QualityVeryHigh"]
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string ShadowRangeWord(int metres) => metres switch
|
||||||
|
{
|
||||||
|
<= 25 => App.Loc["RangeVeryLow"],
|
||||||
|
<= 50 => App.Loc["RangeLow"],
|
||||||
|
<= 150 => App.Loc["RangeModerate"],
|
||||||
|
<= 250 => App.Loc["RangeHigh"],
|
||||||
|
_ => App.Loc["RangeVeryHigh"]
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string CabShadowRangeWord(int metres) => metres switch
|
||||||
|
{
|
||||||
|
<= 10 => App.Loc["RangeVeryLow"],
|
||||||
|
<= 20 => App.Loc["RangeLow"],
|
||||||
|
<= 30 => App.Loc["RangeStandard"],
|
||||||
|
<= 50 => App.Loc["RangeHigh"],
|
||||||
|
_ => App.Loc["RangeVeryHigh"]
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string ReflectionsRefreshWord(int fps) => fps switch
|
||||||
|
{
|
||||||
|
<= 5 => App.Loc["QualityVeryLow"],
|
||||||
|
<= 10 => App.Loc["QualityLow"],
|
||||||
|
<= 25 => App.Loc["QualityHigh"],
|
||||||
|
<= 60 => App.Loc["QualityVeryHigh"],
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
MinWidth="720" MinHeight="400">
|
MinWidth="720" MinHeight="400">
|
||||||
<DockPanel Margin="10">
|
<DockPanel Margin="10">
|
||||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*,*,*" RowDefinitions="Auto,Auto,Auto"
|
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*,*,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto"
|
||||||
ColumnSpacing="8" RowSpacing="6" Margin="0,0,0,8">
|
ColumnSpacing="8" RowSpacing="6" Margin="0,0,0,8">
|
||||||
<CheckBox Name="fNameOn" Grid.Row="0" Grid.Column="0" Content="{Binding [TextureInfo], Source={x:Static starterNg:App.Loc}}" />
|
<CheckBox Name="fNameOn" Grid.Row="0" Grid.Column="0" Content="{Binding [TextureInfo], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<TextBox Name="fName" Grid.Row="0" Grid.Column="1" Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}" />
|
<TextBox Name="fName" Grid.Row="0" Grid.Column="1" PlaceholderText="{Binding [Search], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<CheckBox Name="fOpOn" Grid.Row="0" Grid.Column="2" Content="{Binding [TexOperator], Source={x:Static starterNg:App.Loc}}" />
|
<CheckBox Name="fOpOn" Grid.Row="0" Grid.Column="2" Content="{Binding [TexOperator], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<TextBox Name="fOp" Grid.Row="0" Grid.Column="3" />
|
<TextBox Name="fOp" Grid.Row="0" Grid.Column="3" />
|
||||||
|
|
||||||
@@ -24,15 +24,44 @@
|
|||||||
<TextBox Name="fStation" Grid.Row="2" Grid.Column="1" />
|
<TextBox Name="fStation" Grid.Row="2" Grid.Column="1" />
|
||||||
<CheckBox Name="fPhotoOn" Grid.Row="2" Grid.Column="2" Content="{Binding [TexPhoto], Source={x:Static starterNg:App.Loc}}" />
|
<CheckBox Name="fPhotoOn" Grid.Row="2" Grid.Column="2" Content="{Binding [TexPhoto], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<TextBox Name="fPhoto" Grid.Row="2" Grid.Column="3" />
|
<TextBox Name="fPhoto" Grid.Row="2" Grid.Column="3" />
|
||||||
|
|
||||||
|
<CheckBox Name="fModelOn" Grid.Row="3" Grid.Column="0" Content="{Binding [Model], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<TextBox Name="fModel" Grid.Row="3" Grid.Column="1" />
|
||||||
|
|
||||||
|
<CheckBox Name="fRevFromOn" Grid.Row="4" Grid.Column="0" Content="{Binding [RevisionFrom], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<TextBox Name="fRevFrom" Grid.Row="4" Grid.Column="1" PlaceholderText="RRRR-MM-DD" />
|
||||||
|
<CheckBox Name="fRevToOn" Grid.Row="4" Grid.Column="2" Content="{Binding [RevisionTo], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
<TextBox Name="fRevTo" Grid.Row="4" Grid.Column="3" PlaceholderText="RRRR-MM-DD" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<TextBlock Name="statusText" DockPanel.Dock="Bottom" Margin="0,6,0,0"
|
<TextBlock Name="statusText" DockPanel.Dock="Bottom" Margin="0,6,0,0"
|
||||||
FontSize="11" Opacity="0.75" />
|
FontSize="11" Opacity="0.75" />
|
||||||
|
|
||||||
|
<Grid DockPanel.Dock="Left" Width="260" RowDefinitions="*,4,*">
|
||||||
|
<ListBox Name="typeList" Grid.Row="0" Classes="Compact" />
|
||||||
|
<GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch"
|
||||||
|
Background="Transparent" ResizeDirection="Rows" />
|
||||||
|
<ListBox Name="modelList" Grid.Row="2" Classes="Compact">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid ColumnDefinitions="28,90,*" ColumnSpacing="4">
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding No}" Opacity="0.6" />
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding Klasa}" TextTrimming="CharacterEllipsis" />
|
||||||
|
<TextBlock Grid.Column="2" Text="{Binding Sciezka}" Opacity="0.75"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<GridSplitter Name="spSplitter" DockPanel.Dock="Left" Width="4"
|
||||||
|
Background="Transparent" ResizeDirection="Columns" />
|
||||||
|
|
||||||
<ListBox Name="textureList" DoubleTapped="TextureList_OnDoubleTapped">
|
<ListBox Name="textureList" DoubleTapped="TextureList_OnDoubleTapped">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid ColumnDefinitions="220,*,160,100,140" ColumnSpacing="8">
|
<Grid ColumnDefinitions="1.4*,0.9*,1.2*,90,*" ColumnSpacing="8">
|
||||||
<TextBlock Grid.Column="0" Text="{Binding Skin}" TextTrimming="CharacterEllipsis" />
|
<TextBlock Grid.Column="0" Text="{Binding Skin}" TextTrimming="CharacterEllipsis" />
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Mini}" Opacity="0.85" TextTrimming="CharacterEllipsis" />
|
<TextBlock Grid.Column="1" Text="{Binding Mini}" Opacity="0.85" TextTrimming="CharacterEllipsis" />
|
||||||
<TextBlock Grid.Column="2" Text="{Binding Operator}" Opacity="0.85" TextTrimming="CharacterEllipsis" />
|
<TextBlock Grid.Column="2" Text="{Binding Operator}" Opacity="0.85" TextTrimming="CharacterEllipsis" />
|
||||||
|
|||||||
@@ -28,21 +28,65 @@ public partial class TextureBaseWindow : Window
|
|||||||
_all.Add(new Row(t, _db));
|
_all.Add(new Row(t, _db));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var box in new Control[] { fName, fOp, fMini, fAuthor, fStation, fPhoto })
|
foreach (var box in new Control[] { fName, fOp, fMini, fAuthor, fStation, fPhoto, fModel })
|
||||||
{
|
{
|
||||||
if (box is TextBox tb)
|
if (box is TextBox tb)
|
||||||
tb.TextChanged += (_, _) => Refresh();
|
tb.TextChanged += (_, _) => Refresh();
|
||||||
}
|
}
|
||||||
foreach (var box in new Control[] { fNameOn, fOpOn, fMiniOn, fAuthorOn, fStationOn, fPhotoOn })
|
foreach (var box in new Control[] { fNameOn, fOpOn, fMiniOn, fAuthorOn, fStationOn, fPhotoOn,
|
||||||
|
fModelOn, fRevFromOn, fRevToOn })
|
||||||
{
|
{
|
||||||
if (box is CheckBox cb)
|
if (box is CheckBox cb)
|
||||||
cb.IsCheckedChanged += (_, _) => Refresh();
|
cb.IsCheckedChanged += (_, _) => Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fRevFrom.TextChanged += (_, _) => Refresh();
|
||||||
|
fRevTo.TextChanged += (_, _) => Refresh();
|
||||||
|
|
||||||
|
BuildTypeList();
|
||||||
|
typeList.SelectionChanged += (_, _) => { BuildModelList(); Refresh(); };
|
||||||
|
modelList.SelectionChanged += (_, _) => Refresh();
|
||||||
|
|
||||||
textureList.ContextRequested += TextureList_OnContextRequested;
|
textureList.ContextRequested += TextureList_OnContextRequested;
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed record ModelRow(string No, string Klasa, string Sciezka);
|
||||||
|
|
||||||
|
private void BuildModelList()
|
||||||
|
{
|
||||||
|
modelList.ItemsSource = null;
|
||||||
|
|
||||||
|
string? cat = (typeList.SelectedItem as ListBoxItem)?.Tag as string;
|
||||||
|
var rows = _all
|
||||||
|
.Where(r => cat == null || string.Equals(r.Category, cat, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(r => r.Mini.Length > 0)
|
||||||
|
.GroupBy(r => r.Mini, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select((g, i) => new ModelRow($"{i + 1}.", g.Key, g.First().Directory))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
modelList.ItemsSource = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime? ParseDate(string? text) =>
|
||||||
|
DateTime.TryParse(text, System.Globalization.CultureInfo.InvariantCulture,
|
||||||
|
System.Globalization.DateTimeStyles.None, out var d) ? d : null;
|
||||||
|
|
||||||
|
private void BuildTypeList()
|
||||||
|
{
|
||||||
|
typeList.Items.Add(new ListBoxItem { Content = App.Loc["CatAll"], Tag = null });
|
||||||
|
|
||||||
|
foreach (string cat in _all.Select(r => r.Category)
|
||||||
|
.Where(c => c.Length > 0)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(c => c, StringComparer.OrdinalIgnoreCase))
|
||||||
|
typeList.Items.Add(new ListBoxItem { Content = cat, Tag = cat });
|
||||||
|
|
||||||
|
typeList.SelectedIndex = 0;
|
||||||
|
BuildModelList();
|
||||||
|
}
|
||||||
|
|
||||||
private void Refresh()
|
private void Refresh()
|
||||||
{
|
{
|
||||||
IEnumerable<Row> q = _all;
|
IEnumerable<Row> q = _all;
|
||||||
@@ -53,6 +97,18 @@ public partial class TextureBaseWindow : Window
|
|||||||
q = Filter(q, fAuthorOn, fAuthor, r => r.Author);
|
q = Filter(q, fAuthorOn, fAuthor, r => r.Author);
|
||||||
q = Filter(q, fStationOn, fStation, r => r.Station);
|
q = Filter(q, fStationOn, fStation, r => r.Station);
|
||||||
q = Filter(q, fPhotoOn, fPhoto, r => r.Photo);
|
q = Filter(q, fPhotoOn, fPhoto, r => r.Photo);
|
||||||
|
q = Filter(q, fModelOn, fModel, r => r.Model);
|
||||||
|
|
||||||
|
if ((typeList.SelectedItem as ListBoxItem)?.Tag is string cat)
|
||||||
|
q = q.Where(r => string.Equals(r.Category, cat, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (modelList.SelectedItem is ModelRow mr)
|
||||||
|
q = q.Where(r => string.Equals(r.Mini, mr.Klasa, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (fRevFromOn.IsChecked == true && ParseDate(fRevFrom.Text) is { } from)
|
||||||
|
q = q.Where(r => r.RevisionDate is { } d && d >= from);
|
||||||
|
if (fRevToOn.IsChecked == true && ParseDate(fRevTo.Text) is { } to)
|
||||||
|
q = q.Where(r => r.RevisionDate is { } d && d <= to);
|
||||||
|
|
||||||
var list = q
|
var list = q
|
||||||
.OrderBy(r => r.Mini, StringComparer.OrdinalIgnoreCase)
|
.OrderBy(r => r.Mini, StringComparer.OrdinalIgnoreCase)
|
||||||
@@ -98,12 +154,23 @@ public partial class TextureBaseWindow : Window
|
|||||||
{
|
{
|
||||||
string dir = Path.Combine(Directory.GetCurrentDirectory(), "dynamic",
|
string dir = Path.Combine(Directory.GetCurrentDirectory(), "dynamic",
|
||||||
row.Texture.Directory.Replace('/', Path.DirectorySeparatorChar).TrimEnd('\\', '/'));
|
row.Texture.Directory.Replace('/', Path.DirectorySeparatorChar).TrimEnd('\\', '/'));
|
||||||
if (!Directory.Exists(dir)) return;
|
if (!Directory.Exists(dir))
|
||||||
|
dir = Path.Combine(Directory.GetCurrentDirectory(), row.Texture.Directory);
|
||||||
|
if (!Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(
|
||||||
|
string.Format(App.Loc["FaultFileNotFound"], dir));
|
||||||
|
return;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(
|
||||||
|
$"{dir}{Environment.NewLine}{Environment.NewLine}{App.Loc["FaultDetail"]} {ex.Message}");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
menu.Items.Add(open);
|
menu.Items.Add(open);
|
||||||
menu.Open(textureList);
|
menu.Open(textureList);
|
||||||
@@ -120,6 +187,10 @@ public partial class TextureBaseWindow : Window
|
|||||||
public string Author { get; }
|
public string Author { get; }
|
||||||
public string Station { get; }
|
public string Station { get; }
|
||||||
public string Photo { get; }
|
public string Photo { get; }
|
||||||
|
public string Model { get; }
|
||||||
|
public string Category { get; }
|
||||||
|
public string Directory { get; }
|
||||||
|
public DateTime? RevisionDate { get; }
|
||||||
|
|
||||||
public Row(VehicleTexture t, VehicleDatabase db)
|
public Row(VehicleTexture t, VehicleDatabase db)
|
||||||
{
|
{
|
||||||
@@ -131,6 +202,11 @@ public partial class TextureBaseWindow : Window
|
|||||||
Author = t.Meta?.TextureAuthor ?? "";
|
Author = t.Meta?.TextureAuthor ?? "";
|
||||||
Station = t.Meta?.Depot ?? "";
|
Station = t.Meta?.Depot ?? "";
|
||||||
Photo = t.Meta?.PhotoAuthor ?? "";
|
Photo = t.Meta?.PhotoAuthor ?? "";
|
||||||
|
Model = t.Model ?? "";
|
||||||
|
Category = t.ResolvedCategory ?? "";
|
||||||
|
Directory = t.Directory ?? "";
|
||||||
|
RevisionDate = DateTime.TryParse(Revision, System.Globalization.CultureInfo.InvariantCulture,
|
||||||
|
System.Globalization.DateTimeStyles.None, out var rev) ? rev : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
129
StarterNG/Views/UartWindow.cs
Normal file
129
StarterNG/Views/UartWindow.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using SettingsModel = StarterNG.Classes.Settings;
|
||||||
|
|
||||||
|
namespace StarterNG.Views;
|
||||||
|
|
||||||
|
public sealed class UartWindow : Window
|
||||||
|
{
|
||||||
|
private readonly CheckBox _enabled = new();
|
||||||
|
private readonly TextBox _port = new() { PlaceholderText = "COM3", MinWidth = 170 };
|
||||||
|
private readonly TextBox _tune = new() { MinWidth = 170 };
|
||||||
|
private readonly CheckBox _debug = new();
|
||||||
|
private readonly CheckBox _main = new();
|
||||||
|
private readonly CheckBox _scnd = new();
|
||||||
|
private readonly CheckBox _train = new();
|
||||||
|
private readonly CheckBox _local = new();
|
||||||
|
private readonly CheckBox _radioVolume = new();
|
||||||
|
private readonly CheckBox _radioChannel = new();
|
||||||
|
|
||||||
|
public UartWindow()
|
||||||
|
{
|
||||||
|
Title = App.Loc["UartSection"];
|
||||||
|
Width = 430;
|
||||||
|
SizeToContent = SizeToContent.Height;
|
||||||
|
CanResize = false;
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||||
|
|
||||||
|
_enabled.Content = App.Loc["UartEnable"];
|
||||||
|
_debug.Content = App.Loc["UartDebug"];
|
||||||
|
_main.Content = App.Loc["UartMain"];
|
||||||
|
_scnd.Content = App.Loc["UartScnd"];
|
||||||
|
_train.Content = App.Loc["UartTrain"];
|
||||||
|
_local.Content = App.Loc["UartLocal"];
|
||||||
|
_radioVolume.Content = App.Loc["UartRadioVolume"];
|
||||||
|
_radioChannel.Content = App.Loc["UartRadioChannel"];
|
||||||
|
|
||||||
|
Load();
|
||||||
|
|
||||||
|
var root = new StackPanel { Margin = new Avalonia.Thickness(16), Spacing = 8 };
|
||||||
|
root.Children.Add(_enabled);
|
||||||
|
root.Children.Add(Row(App.Loc["UartPort"], _port));
|
||||||
|
root.Children.Add(Row(App.Loc["UartTune"], _tune));
|
||||||
|
root.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = App.Loc["UartHint"],
|
||||||
|
FontSize = 11,
|
||||||
|
Opacity = 0.65,
|
||||||
|
TextWrapping = TextWrapping.Wrap
|
||||||
|
});
|
||||||
|
root.Children.Add(_debug);
|
||||||
|
|
||||||
|
root.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = App.Loc["UartFeature"],
|
||||||
|
FontWeight = FontWeight.Bold,
|
||||||
|
Margin = new Avalonia.Thickness(0, 6, 0, 0)
|
||||||
|
});
|
||||||
|
var top = new UniformGrid { Columns = 2, Margin = new Avalonia.Thickness(0, 0, 0, 2) };
|
||||||
|
top.Children.Add(_main);
|
||||||
|
top.Children.Add(_scnd);
|
||||||
|
top.Children.Add(_train);
|
||||||
|
top.Children.Add(_local);
|
||||||
|
root.Children.Add(top);
|
||||||
|
|
||||||
|
var mid = new UniformGrid { Columns = 2 };
|
||||||
|
mid.Children.Add(_radioVolume);
|
||||||
|
mid.Children.Add(_radioChannel);
|
||||||
|
root.Children.Add(mid);
|
||||||
|
|
||||||
|
var save = new Button
|
||||||
|
{
|
||||||
|
Content = App.Loc["UartSaveClose"],
|
||||||
|
MinWidth = 140,
|
||||||
|
Margin = new Avalonia.Thickness(0, 10, 0, 0),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||||
|
};
|
||||||
|
save.Classes.Add("Accent");
|
||||||
|
save.Click += (_, _) => { Store(); Close(); };
|
||||||
|
root.Children.Add(save);
|
||||||
|
|
||||||
|
Content = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Control Row(string label, Control input)
|
||||||
|
{
|
||||||
|
var sp = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
|
||||||
|
sp.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = label,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
MinWidth = 110
|
||||||
|
});
|
||||||
|
sp.Children.Add(input);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Load()
|
||||||
|
{
|
||||||
|
var s = SettingsModel.Instance;
|
||||||
|
_enabled.IsChecked = s.UartEnabled;
|
||||||
|
_port.Text = s.UartPort;
|
||||||
|
_tune.Text = s.UartTune;
|
||||||
|
_debug.IsChecked = s.UartDebug;
|
||||||
|
_main.IsChecked = s.UartMain;
|
||||||
|
_scnd.IsChecked = s.UartScnd;
|
||||||
|
_train.IsChecked = s.UartTrain;
|
||||||
|
_local.IsChecked = s.UartLocal;
|
||||||
|
_radioVolume.IsChecked = s.UartRadioVolume;
|
||||||
|
_radioChannel.IsChecked = s.UartRadioChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Store()
|
||||||
|
{
|
||||||
|
var s = SettingsModel.Instance;
|
||||||
|
s.UartEnabled = _enabled.IsChecked == true;
|
||||||
|
s.UartPort = _port.Text ?? "";
|
||||||
|
s.UartTune = _tune.Text ?? "";
|
||||||
|
s.UartDebug = _debug.IsChecked == true;
|
||||||
|
s.UartMain = _main.IsChecked == true;
|
||||||
|
s.UartScnd = _scnd.IsChecked == true;
|
||||||
|
s.UartTrain = _train.IsChecked == true;
|
||||||
|
s.UartLocal = _local.IsChecked == true;
|
||||||
|
s.UartRadioVolume = _radioVolume.IsChecked == true;
|
||||||
|
s.UartRadioChannel = _radioChannel.IsChecked == true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,8 @@
|
|||||||
<String key="Time">Čas</String>
|
<String key="Time">Čas</String>
|
||||||
<String key="CurrentTime">Nyní</String>
|
<String key="CurrentTime">Nyní</String>
|
||||||
<String key="Day">Den v roce</String>
|
<String key="Day">Den v roce</String>
|
||||||
|
<String key="Date">Datum</String>
|
||||||
|
<String key="DayOfYear">den {0}</String>
|
||||||
<String key="Sky">Obloha</String>
|
<String key="Sky">Obloha</String>
|
||||||
<String key="Fog">Mlha / viditelnost</String>
|
<String key="Fog">Mlha / viditelnost</String>
|
||||||
<String key="Season">Roční období</String>
|
<String key="Season">Roční období</String>
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
<String key="SeasonSummer">Léto</String>
|
<String key="SeasonSummer">Léto</String>
|
||||||
<String key="SeasonAutumn">Podzim</String>
|
<String key="SeasonAutumn">Podzim</String>
|
||||||
<String key="SeasonWinter">Zima</String>
|
<String key="SeasonWinter">Zima</String>
|
||||||
|
<String key="OvercastRandom">Náhodné</String>
|
||||||
<String key="OvercastClear">Jasno</String>
|
<String key="OvercastClear">Jasno</String>
|
||||||
<String key="OvercastFair">Skoro jasno</String>
|
<String key="OvercastFair">Skoro jasno</String>
|
||||||
<String key="OvercastLight">Slabá oblačnost</String>
|
<String key="OvercastLight">Slabá oblačnost</String>
|
||||||
@@ -51,7 +54,8 @@
|
|||||||
<String key="OvercastHeavy">Silná oblačnost</String>
|
<String key="OvercastHeavy">Silná oblačnost</String>
|
||||||
<String key="OvercastCloudy">Zataženo</String>
|
<String key="OvercastCloudy">Zataženo</String>
|
||||||
<String key="OvercastStorm">Bouřka</String>
|
<String key="OvercastStorm">Bouřka</String>
|
||||||
<String key="WeatherHint">Změny počasí se při spuštění zapíší do scenérie.</String>
|
<String key="WeatherDateTime">Datum a čas</String>
|
||||||
|
<String key="WeatherConditions">Povětrnostní podmínky</String>
|
||||||
<String key="Consist">Souprava</String>
|
<String key="Consist">Souprava</String>
|
||||||
<String key="Start">Start</String>
|
<String key="Start">Start</String>
|
||||||
<String key="StartNoScenery">Vyberte scenérii</String>
|
<String key="StartNoScenery">Vyberte scenérii</String>
|
||||||
@@ -148,40 +152,39 @@
|
|||||||
<String key="Coupling">Spřáhlo</String>
|
<String key="Coupling">Spřáhlo</String>
|
||||||
<String key="MapConsists">Soupravy ve scenérii</String>
|
<String key="MapConsists">Soupravy ve scenérii</String>
|
||||||
<String key="NoSceneryConsists">Vyberte scenérii na kartě Scénáře.</String>
|
<String key="NoSceneryConsists">Vyberte scenérii na kartě Scénáře.</String>
|
||||||
<String key="BrakeMode">Režim brzdy</String>
|
<String key="BrakeMode">Nastavení brzdiče</String>
|
||||||
<String key="BrakeLoad">Nastavení zatížení</String>
|
<String key="BrakeLoad">Přispůsobení</String>
|
||||||
<String key="BrakeSwitch">Přepínač brzdy</String>
|
<String key="BrakeSwitch">Brzdič</String>
|
||||||
<String key="None">—</String>
|
<String key="None">—</String>
|
||||||
|
|
||||||
<!-- Coupling mask bits -->
|
<!-- Coupling mask bits -->
|
||||||
<String key="CplMechanical">Mechanické</String>
|
<String key="CplMechanical">Mechanicky</String>
|
||||||
<String key="CplBrake">Pneumatické 5 atm (brzdy)</String>
|
<String key="CplBrake">Brzda</String>
|
||||||
<String key="CplControl">Vícečlenné řízení</String>
|
<String key="CplControl">Ukrojení</String>
|
||||||
<String key="CplHighVoltage">Vysoké napětí</String>
|
<String key="CplHighVoltage">Vysoké napětí</String>
|
||||||
<String key="CplGangway">Průchod mezi vozidly</String>
|
<String key="CplGangway">Přechod</String>
|
||||||
<String key="CplAuxPneumatic">Pomocné pneumatické 8 atm</String>
|
<String key="CplAuxPneumatic">Vzduch 8atm</String>
|
||||||
<String key="CplHeating">Topení</String>
|
<String key="CplHeating">Topení</String>
|
||||||
<String key="CplWorkshop">Dílenské spřáhlo (zámek rozpojení)</String>
|
<String key="CplWorkshop">Blokáda</String>
|
||||||
|
|
||||||
<!-- Brake modes -->
|
<!-- Brake modes -->
|
||||||
<String key="BrakeFreight">Nákladní (G)</String>
|
<String key="BrakeActingNone">Není nastaven</String>
|
||||||
<String key="BrakePassenger">Osobní (P)</String>
|
<String key="BrakeFreight">G - nákladní režim</String>
|
||||||
<String key="BrakeExpress">Rychlík (R)</String>
|
<String key="BrakePassenger">P - osobní režim</String>
|
||||||
<String key="BrakeExpressMg">Rychlík + magnetická kolejnicová (R+Mg)</String>
|
<String key="BrakeExpress">R - rychlíkový režim</String>
|
||||||
<String key="BrakeNoAir">Start bez vzduchu (Q)</String>
|
<String key="BrakeExpressMg">R + magn. kolejová brzda</String>
|
||||||
<String key="BrakeOff">Brzdy odpojeny (O)</String>
|
<String key="BrakeNoAir">Q - bez vzduchu</String>
|
||||||
<String key="BrakeAuto">Automaticky (A)</String>
|
|
||||||
|
|
||||||
<!-- Load adaptation -->
|
<!-- Load adaptation -->
|
||||||
<String key="LoadEmpty">Prázdný (T)</String>
|
<String key="LoadNeutral">N - neutralní nastavení</String>
|
||||||
<String key="LoadMedium">Ložený I — střední (H)</String>
|
<String key="LoadEmpty">T - prázdný</String>
|
||||||
<String key="LoadFull">Ložený II (F)</String>
|
<String key="LoadMedium">H - naložen I</String>
|
||||||
<String key="LoadAuto">Automaticky (A)</String>
|
<String key="LoadFull">F - naložen II</String>
|
||||||
|
|
||||||
<!-- Brake switch -->
|
<!-- Brake switch -->
|
||||||
<String key="SwitchOff">Vypnuto (0)</String>
|
<String key="SwitchNormal">N - brzdič zabržděný</String>
|
||||||
<String key="SwitchOff10">Vypnuto s pravděpodobností 10 % (1)</String>
|
<String key="SwitchOff">0 - odbržděný, odpuštěný</String>
|
||||||
<String key="SwitchOn">Zapnuto (A)</String>
|
<String key="SwitchOff10">1 - odbržděný, odp. z 10% pravd.</String>
|
||||||
|
|
||||||
<!-- Damage (wheel parameters) -->
|
<!-- Damage (wheel parameters) -->
|
||||||
<String key="Damage">Poškození</String>
|
<String key="Damage">Poškození</String>
|
||||||
@@ -192,6 +195,9 @@
|
|||||||
<String key="LoadType">Druh nákladu</String>
|
<String key="LoadType">Druh nákladu</String>
|
||||||
<String key="LoadCount">Množství</String>
|
<String key="LoadCount">Množství</String>
|
||||||
<String key="LoadHint">Pro naložení vozidla zadejte množství větší než 0.</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="WagonNumber">Číslo vozu</String>
|
<String key="WagonNumber">Číslo vozu</String>
|
||||||
<String key="LoadCopyPrev">Kopírovat náklad z předchozího vozidla</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="LoadMax">Maximální možný náklad</String>
|
||||||
@@ -242,6 +248,10 @@
|
|||||||
<String key="ComingSoon">Možnosti úprav budou brzy k dispozici.</String>
|
<String key="ComingSoon">Možnosti úprav budou brzy k dispozici.</String>
|
||||||
<String key="Split">Rozdělit</String>
|
<String key="Split">Rozdělit</String>
|
||||||
<String key="TipSplit">Rozdělit jednotku na jednotlivé vozy</String>
|
<String key="TipSplit">Rozdělit jednotku na jednotlivé vozy</String>
|
||||||
|
<String key="AppliesToUnit">Společné pro celou jednotku</String>
|
||||||
|
<String key="CouplingUnitRear">Spřáhlo na konci jednotky</String>
|
||||||
|
<String key="Join">Spojit</String>
|
||||||
|
<String key="TipJoin">Spojit toto vozidlo s následujícím do jedné jednotky</String>
|
||||||
<String key="AddVehicle">Přidat</String>
|
<String key="AddVehicle">Přidat</String>
|
||||||
<String key="TipAddVehicle">Přidat toto vozidlo do soupravy</String>
|
<String key="TipAddVehicle">Přidat toto vozidlo do soupravy</String>
|
||||||
<String key="TipAddUnit">Přidat celou jednotku do soupravy</String>
|
<String key="TipAddUnit">Přidat celou jednotku do soupravy</String>
|
||||||
@@ -320,10 +330,28 @@
|
|||||||
<String key="MaxTexCabResolution">Maximální rozlišení textur kabiny:</String>
|
<String key="MaxTexCabResolution">Maximální rozlišení textur kabiny:</String>
|
||||||
<String key="TexFilteringQuality">Kvalita filtrování textur:</String>
|
<String key="TexFilteringQuality">Kvalita filtrování textur:</String>
|
||||||
<String key="Multisampling">Multisampling:</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>
|
||||||
|
<String key="QualityHigh">Vysoká</String>
|
||||||
|
<String key="QualityVeryHigh">Velmi vysoká</String>
|
||||||
|
<String key="MsaaNone">Žádné</String>
|
||||||
|
<String key="QualityModerate">Mírná</String>
|
||||||
|
<String key="QualityStandard">Standartní</String>
|
||||||
|
<String key="RangeVeryLow">Velmi nízký</String>
|
||||||
|
<String key="RangeLow">Nízký</String>
|
||||||
|
<String key="RangeNormal">Normální</String>
|
||||||
|
<String key="RangeModerate">Mírný</String>
|
||||||
|
<String key="RangeStandard">Standartní</String>
|
||||||
|
<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="DynamicLights">Dynamické zdroje světla:</String>
|
||||||
<String key="RenderRange">Dohlednost vykreslování:</String>
|
<String key="RenderRange">Dohlednost vykreslování:</String>
|
||||||
<String key="FpsLimitEnable">Omezit snímkovou frekvenci</String>
|
<String key="FpsLimitEnable">Omezit snímkovou frekvenci</String>
|
||||||
<String key="ShadowAngleLimit">Omezení délky stínů</String>
|
<String key="ShadowAngleLimit">Omezení délky stínů</String>
|
||||||
|
<String key="ShadowLimitNone">Bez omezení</String>
|
||||||
|
<String key="ShadowLimitShort">Zkrácené</String>
|
||||||
<String key="FullscreenWindowed">Automatické rozlišení (celá obrazovka bez okrajů)</String>
|
<String key="FullscreenWindowed">Automatické rozlišení (celá obrazovka bez okrajů)</String>
|
||||||
<String key="RenderAngleVulkan">Použít renderer ANGLE Vulkan</String>
|
<String key="RenderAngleVulkan">Použít renderer ANGLE Vulkan</String>
|
||||||
<String key="VSync">VSync</String>
|
<String key="VSync">VSync</String>
|
||||||
@@ -427,4 +455,100 @@
|
|||||||
<String key="Save">Uložit</String>
|
<String key="Save">Uložit</String>
|
||||||
<String key="Reset">Obnovit</String>
|
<String key="Reset">Obnovit</String>
|
||||||
<String key="SettingsSaved">Nastavení uloženo</String>
|
<String key="SettingsSaved">Nastavení uloženo</String>
|
||||||
|
|
||||||
|
<!-- Errors / diagnostics -->
|
||||||
|
<String key="FaultTitle">Chyba</String>
|
||||||
|
<String key="FaultDetail">Podrobnosti chyby:</String>
|
||||||
|
<String key="FaultFileNotFound">Soubor nenalezen: {0}</String>
|
||||||
|
<String key="FaultLoadData">Nepodařilo se načíst data simulátoru.</String>
|
||||||
|
<String key="FaultBadInstall">Instalace simulátoru může být neúplná.</String>
|
||||||
|
<String key="FaultNoDir">Chybí složka {0}</String>
|
||||||
|
<String key="FaultNoWeights">Chybí informace o hmotnostech nákladu.</String>
|
||||||
|
<String key="FaultNoScenery">Nebyly nalezeny žádné scénáře.</String>
|
||||||
|
<String key="FaultNoVehicles">Nebyla nalezena žádná vozidla.</String>
|
||||||
|
<String key="FaultNoPhysics">Nebyla nalezena data vozového parku.</String>
|
||||||
|
<String key="FaultNoExe">Spustitelný soubor simulátoru nebyl nalezen.</String>
|
||||||
|
|
||||||
|
<!-- About -->
|
||||||
|
<String key="AboutTitle">O programu</String>
|
||||||
|
<String key="AboutForSimulator">pro simulátor MaSzyna</String>
|
||||||
|
<String key="AboutChangelog">Seznam změn:</String>
|
||||||
|
<String key="AboutNoChangelog">Soubor se seznamem změn nebyl nalezen.</String>
|
||||||
|
<String key="About">O programu</String>
|
||||||
|
<String key="Close">Zavřít</String>
|
||||||
|
<String key="SettingsPath">Nastavení načteno z:</String>
|
||||||
|
|
||||||
|
<!-- Vehicle info -->
|
||||||
|
<String key="GeneralInfo">Obecné</String>
|
||||||
|
<String key="Model">Model</String>
|
||||||
|
<String key="VMax">VMax</String>
|
||||||
|
<String key="Texture">Textura</String>
|
||||||
|
|
||||||
|
<!-- Scenery tab -->
|
||||||
|
<String key="SceneryConsists">Soupravy na scenérii</String>
|
||||||
|
<String key="SceneryFaults">Chyby ve scénáři:</String>
|
||||||
|
|
||||||
|
<!-- Warehouse / vehicle panels -->
|
||||||
|
<String key="WarehouseConsists">Soupravy ve skladu</String>
|
||||||
|
<String key="AddToWarehouse">Přidat do skladu</String>
|
||||||
|
<String key="RemoveFromWarehouse">Odebrat ze skladu</String>
|
||||||
|
<String key="WarehouseApply">Nahradit soupravou ze skladu</String>
|
||||||
|
<String key="WarehouseImportNothing">Nebyly nalezeny žádné soupravy k importu.</String>
|
||||||
|
<String key="AboutTexture">O textuře</String>
|
||||||
|
<String key="NoTextureInfo">Žádné informace o textuře.</String>
|
||||||
|
|
||||||
|
<!-- Vehicle configuration / coupler -->
|
||||||
|
<String key="Configuration">Konfigurace</String>
|
||||||
|
<String key="RemoveVehicle">Odebrat vozidlo</String>
|
||||||
|
<String key="AutoCoupler">Auto</String>
|
||||||
|
<String key="TipAutoCoupler">Zvolit spřáhla automaticky pro celou soupravu</String>
|
||||||
|
<String key="CouplingLastVehicle">Spřáhlo na konci soupravy — za ním není vozidlo.</String>
|
||||||
|
<String key="ChangeName">Přejmenovat...</String>
|
||||||
|
<String key="LoadCopyToAll">Použít tento náklad na všechna následující vozidla, která ho přijmou</String>
|
||||||
|
<String key="WarehouseAppend">Připojit soupravu ze skladu</String>
|
||||||
|
<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="DebugLogVisible">Zobrazovat průběh simulace</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>
|
||||||
|
<String key="PyPriorityIdle">Žádná</String>
|
||||||
|
<String key="PyPriorityOff">Vypnutá</String>
|
||||||
|
|
||||||
|
<String key="MassBrutto">Hrubá hmotnost</String>
|
||||||
|
<String key="VisibilityGood">Dobrá</String>
|
||||||
|
<String key="VisibilityBad">Špatná</String>
|
||||||
|
<String key="SoundPhysics">Zvuk | Fyzika</String>
|
||||||
|
<String key="DriverHead">Strojvedoucí (kabina A)</String>
|
||||||
|
<String key="DriverRear">Strojvedoucí (kabina B)</String>
|
||||||
|
<String key="DriverPassenger">Cestující</String>
|
||||||
|
<String key="DriverNobody">Nikdo</String>
|
||||||
|
<String key="RemoveAllTrains">Odebrat všechny soupravy ze scenérie</String>
|
||||||
|
<String key="RemoveAllTrainsConfirm">Odebrat vozidla ze všech souprav na této scenérii?</String>
|
||||||
|
<String key="TexWorks">Revize:</String>
|
||||||
|
<String key="RevisionFrom">Revize od:</String>
|
||||||
|
<String key="RevisionTo">Revize do:</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>
|
||||||
|
<String key="CatAll">Vše</String>
|
||||||
|
<String key="GfxColResolution">Rozlišení a textury</String>
|
||||||
|
<String key="GfxColRendering">Vykreslování</String>
|
||||||
|
<String key="GfxColShadows">Stíny</String>
|
||||||
|
<String key="GfxColReflections">Odrazy a pohled</String>
|
||||||
|
<String key="GfxColScripts">Python skripty</String>
|
||||||
|
<String key="UartHint">Název portu / neměnit / frekvence odesílání dat</String>
|
||||||
|
<String key="UartFeature">UARTfeature:</String>
|
||||||
|
<String key="UartSaveClose">Uložit a zavřít</String>
|
||||||
|
<String key="PickCategoryFirst">Nejprve vyberte kategorii nebo třídu vozidel.</String>
|
||||||
|
<String key="HideArchivalSceneries">Skrýt archivní scénáře</String>
|
||||||
|
<String key="Tools">Nástroje</String>
|
||||||
|
<String key="AdvancedSettings">Pokročilá nastavení</String>
|
||||||
|
<String key="AdvLogPage">Deník událostí</String>
|
||||||
|
<String key="AdvClearLog">Vymazat log</String>
|
||||||
|
<String key="ConsistSchedule">Sestava</String>
|
||||||
</Language>
|
</Language>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
<String key="Time">Time</String>
|
<String key="Time">Time</String>
|
||||||
<String key="CurrentTime">Now</String>
|
<String key="CurrentTime">Now</String>
|
||||||
<String key="Day">Day of year</String>
|
<String key="Day">Day of year</String>
|
||||||
|
<String key="Date">Date</String>
|
||||||
|
<String key="DayOfYear">day {0}</String>
|
||||||
<String key="Sky">Sky</String>
|
<String key="Sky">Sky</String>
|
||||||
<String key="Fog">Fog / visibility</String>
|
<String key="Fog">Fog / visibility</String>
|
||||||
<String key="Season">Season</String>
|
<String key="Season">Season</String>
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
<String key="SeasonSummer">Summer</String>
|
<String key="SeasonSummer">Summer</String>
|
||||||
<String key="SeasonAutumn">Autumn</String>
|
<String key="SeasonAutumn">Autumn</String>
|
||||||
<String key="SeasonWinter">Winter</String>
|
<String key="SeasonWinter">Winter</String>
|
||||||
|
<String key="OvercastRandom">Random</String>
|
||||||
<String key="OvercastClear">Clear</String>
|
<String key="OvercastClear">Clear</String>
|
||||||
<String key="OvercastFair">Fair</String>
|
<String key="OvercastFair">Fair</String>
|
||||||
<String key="OvercastLight">Light clouds</String>
|
<String key="OvercastLight">Light clouds</String>
|
||||||
@@ -51,7 +54,8 @@
|
|||||||
<String key="OvercastHeavy">Heavy clouds</String>
|
<String key="OvercastHeavy">Heavy clouds</String>
|
||||||
<String key="OvercastCloudy">Cloudy</String>
|
<String key="OvercastCloudy">Cloudy</String>
|
||||||
<String key="OvercastStorm">Storm</String>
|
<String key="OvercastStorm">Storm</String>
|
||||||
<String key="WeatherHint">Weather changes are written to the scenery on launch.</String>
|
<String key="WeatherDateTime">Date and time</String>
|
||||||
|
<String key="WeatherConditions">Conditions</String>
|
||||||
<String key="Consist">Consist</String>
|
<String key="Consist">Consist</String>
|
||||||
<String key="Start">Start</String>
|
<String key="Start">Start</String>
|
||||||
<String key="StartNoScenery">Select a scenery</String>
|
<String key="StartNoScenery">Select a scenery</String>
|
||||||
@@ -148,40 +152,39 @@
|
|||||||
<String key="Coupling">Coupling</String>
|
<String key="Coupling">Coupling</String>
|
||||||
<String key="MapConsists">Consists on the scenery</String>
|
<String key="MapConsists">Consists on the scenery</String>
|
||||||
<String key="NoSceneryConsists">Select a scenery in the Scenarios tab.</String>
|
<String key="NoSceneryConsists">Select a scenery in the Scenarios tab.</String>
|
||||||
<String key="BrakeMode">Brake mode</String>
|
<String key="BrakeMode">Brake setting</String>
|
||||||
<String key="BrakeLoad">Load setting</String>
|
<String key="BrakeLoad">Brake adjustment</String>
|
||||||
<String key="BrakeSwitch">Brake switch</String>
|
<String key="BrakeSwitch">Brake switch</String>
|
||||||
<String key="None">—</String>
|
<String key="None">—</String>
|
||||||
|
|
||||||
<!-- Coupling mask bits -->
|
<!-- Coupling mask bits -->
|
||||||
<String key="CplMechanical">Mechanical</String>
|
<String key="CplMechanical">Mechanical</String>
|
||||||
<String key="CplBrake">Pneumatic 5 atm (brakes)</String>
|
<String key="CplBrake">Brake</String>
|
||||||
<String key="CplControl">Multiple-unit control</String>
|
<String key="CplControl">Connector</String>
|
||||||
<String key="CplHighVoltage">High voltage</String>
|
<String key="CplHighVoltage">High voltage</String>
|
||||||
<String key="CplGangway">Gangway between vehicles</String>
|
<String key="CplGangway">Passage</String>
|
||||||
<String key="CplAuxPneumatic">Auxiliary pneumatic 8 atm</String>
|
<String key="CplAuxPneumatic">Air 8atm</String>
|
||||||
<String key="CplHeating">Heating</String>
|
<String key="CplHeating">Heating</String>
|
||||||
<String key="CplWorkshop">Workshop coupling (uncouple lock)</String>
|
<String key="CplWorkshop">Lock</String>
|
||||||
|
|
||||||
<!-- Brake modes -->
|
<!-- Brake modes -->
|
||||||
<String key="BrakeFreight">Freight (G)</String>
|
<String key="BrakeActingNone">None</String>
|
||||||
<String key="BrakePassenger">Passenger (P)</String>
|
<String key="BrakeFreight">G - Cargo</String>
|
||||||
<String key="BrakeExpress">Express (R)</String>
|
<String key="BrakePassenger">P - Passenger</String>
|
||||||
<String key="BrakeExpressMg">Express + magnetic rail (R+Mg)</String>
|
<String key="BrakeExpress">R - Rapid</String>
|
||||||
<String key="BrakeNoAir">Start without air (Q)</String>
|
<String key="BrakeExpressMg">R + magn. rail brake</String>
|
||||||
<String key="BrakeOff">Brakes disconnected (O)</String>
|
<String key="BrakeNoAir">Q - without air</String>
|
||||||
<String key="BrakeAuto">Automatic (A)</String>
|
|
||||||
|
|
||||||
<!-- Load adaptation -->
|
<!-- Load adaptation -->
|
||||||
<String key="LoadEmpty">Empty (T)</String>
|
<String key="LoadNeutral">N - neutral</String>
|
||||||
<String key="LoadMedium">Loaded I — medium (H)</String>
|
<String key="LoadEmpty">T - empty</String>
|
||||||
<String key="LoadFull">Loaded II (F)</String>
|
<String key="LoadMedium">H - loaded I</String>
|
||||||
<String key="LoadAuto">Automatic (A)</String>
|
<String key="LoadFull">F - loaded II</String>
|
||||||
|
|
||||||
<!-- Brake switch -->
|
<!-- Brake switch -->
|
||||||
<String key="SwitchOff">Off (0)</String>
|
<String key="SwitchNormal">N - On</String>
|
||||||
<String key="SwitchOff10">Off with 10% chance (1)</String>
|
<String key="SwitchOff">0 - Off, slack</String>
|
||||||
<String key="SwitchOn">On (A)</String>
|
<String key="SwitchOff10">1 - Off, slack (10% chance)</String>
|
||||||
|
|
||||||
<!-- Damage (wheel parameters) -->
|
<!-- Damage (wheel parameters) -->
|
||||||
<String key="Damage">Damage</String>
|
<String key="Damage">Damage</String>
|
||||||
@@ -192,6 +195,9 @@
|
|||||||
<String key="LoadType">Load type</String>
|
<String key="LoadType">Load type</String>
|
||||||
<String key="LoadCount">Amount</String>
|
<String key="LoadCount">Amount</String>
|
||||||
<String key="LoadHint">Set an amount above 0 to load the vehicle.</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="WagonNumber">Wagon number</String>
|
<String key="WagonNumber">Wagon number</String>
|
||||||
<String key="LoadCopyPrev">Copy load from previous vehicle</String>
|
<String key="LoadCopyPrev">Copy load from previous vehicle</String>
|
||||||
<String key="LoadMax">Maximum possible load</String>
|
<String key="LoadMax">Maximum possible load</String>
|
||||||
@@ -206,7 +212,7 @@
|
|||||||
<String key="VehicleCount">Vehicles</String>
|
<String key="VehicleCount">Vehicles</String>
|
||||||
<String key="Track">Track</String>
|
<String key="Track">Track</String>
|
||||||
<String key="EmptyTrainset">No vehicles. (Track: {0})</String>
|
<String key="EmptyTrainset">No vehicles. (Track: {0})</String>
|
||||||
<String key="HideArchivalVehicles">Hide archival vehicles</String>
|
<String key="HideArchivalVehicles">Hide archived vehicles</String>
|
||||||
<String key="RemoveSelectedVehicle">Remove vehicle</String>
|
<String key="RemoveSelectedVehicle">Remove vehicle</String>
|
||||||
<String key="RandomizeTextures">Randomize textures</String>
|
<String key="RandomizeTextures">Randomize textures</String>
|
||||||
<String key="TipRandomizeTextures">Pick similar textures by class / rules</String>
|
<String key="TipRandomizeTextures">Pick similar textures by class / rules</String>
|
||||||
@@ -242,6 +248,10 @@
|
|||||||
<String key="ComingSoon">Editing options coming soon.</String>
|
<String key="ComingSoon">Editing options coming soon.</String>
|
||||||
<String key="Split">Split</String>
|
<String key="Split">Split</String>
|
||||||
<String key="TipSplit">Split the unit into individual cars</String>
|
<String key="TipSplit">Split the unit into individual cars</String>
|
||||||
|
<String key="AppliesToUnit">Shared by the whole unit</String>
|
||||||
|
<String key="CouplingUnitRear">Coupler at the rear of the unit</String>
|
||||||
|
<String key="Join">Join</String>
|
||||||
|
<String key="TipJoin">Couple this vehicle with the next one into a single unit</String>
|
||||||
<String key="AddVehicle">Add</String>
|
<String key="AddVehicle">Add</String>
|
||||||
<String key="TipAddVehicle">Add this vehicle to the consist</String>
|
<String key="TipAddVehicle">Add this vehicle to the consist</String>
|
||||||
<String key="TipAddUnit">Add the whole unit to the consist</String>
|
<String key="TipAddUnit">Add the whole unit to the consist</String>
|
||||||
@@ -320,10 +330,28 @@
|
|||||||
<String key="MaxTexCabResolution">Maximum cabin texture resolution:</String>
|
<String key="MaxTexCabResolution">Maximum cabin texture resolution:</String>
|
||||||
<String key="TexFilteringQuality">Texture filtering quality:</String>
|
<String key="TexFilteringQuality">Texture filtering quality:</String>
|
||||||
<String key="Multisampling">Multisampling:</String>
|
<String key="Multisampling">Multisampling:</String>
|
||||||
|
<String key="QualityVeryLow">Very low</String>
|
||||||
|
<String key="QualityLow">Low</String>
|
||||||
|
<String key="QualityNormal">Normal</String>
|
||||||
|
<String key="QualityHigh">High</String>
|
||||||
|
<String key="QualityVeryHigh">Very high</String>
|
||||||
|
<String key="MsaaNone">None</String>
|
||||||
|
<String key="QualityModerate">Moderate</String>
|
||||||
|
<String key="QualityStandard">Standard</String>
|
||||||
|
<String key="RangeVeryLow">Very low</String>
|
||||||
|
<String key="RangeLow">Low</String>
|
||||||
|
<String key="RangeNormal">Normal</String>
|
||||||
|
<String key="RangeModerate">Moderate</String>
|
||||||
|
<String key="RangeStandard">Standard</String>
|
||||||
|
<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="DynamicLights">Dynamic light sources:</String>
|
||||||
<String key="RenderRange">Rendering range:</String>
|
<String key="RenderRange">Rendering range:</String>
|
||||||
<String key="FpsLimitEnable">Limit frame rate</String>
|
<String key="FpsLimitEnable">Limit frame rate</String>
|
||||||
<String key="ShadowAngleLimit">Shadow length limit</String>
|
<String key="ShadowAngleLimit">Shadow length limit</String>
|
||||||
|
<String key="ShadowLimitNone">No limit</String>
|
||||||
|
<String key="ShadowLimitShort">Shortened</String>
|
||||||
<String key="FullscreenWindowed">Automatic resolution (borderless fullscreen)</String>
|
<String key="FullscreenWindowed">Automatic resolution (borderless fullscreen)</String>
|
||||||
<String key="RenderAngleVulkan">Use ANGLE Vulkan renderer</String>
|
<String key="RenderAngleVulkan">Use ANGLE Vulkan renderer</String>
|
||||||
<String key="VSync">VSync</String>
|
<String key="VSync">VSync</String>
|
||||||
@@ -427,4 +455,100 @@
|
|||||||
<String key="Save">Save</String>
|
<String key="Save">Save</String>
|
||||||
<String key="Reset">Reset</String>
|
<String key="Reset">Reset</String>
|
||||||
<String key="SettingsSaved">Settings saved</String>
|
<String key="SettingsSaved">Settings saved</String>
|
||||||
|
|
||||||
|
<!-- Errors / diagnostics -->
|
||||||
|
<String key="FaultTitle">Error</String>
|
||||||
|
<String key="FaultDetail">Error details:</String>
|
||||||
|
<String key="FaultFileNotFound">File not found: {0}</String>
|
||||||
|
<String key="FaultLoadData">Failed to load simulator data.</String>
|
||||||
|
<String key="FaultBadInstall">The simulator installation may be incomplete.</String>
|
||||||
|
<String key="FaultNoDir">Missing folder {0}</String>
|
||||||
|
<String key="FaultNoWeights">No load weight information.</String>
|
||||||
|
<String key="FaultNoScenery">No scenarios found.</String>
|
||||||
|
<String key="FaultNoVehicles">No vehicles found.</String>
|
||||||
|
<String key="FaultNoPhysics">No rolling stock data found.</String>
|
||||||
|
<String key="FaultNoExe">Simulator executable not found.</String>
|
||||||
|
|
||||||
|
<!-- About -->
|
||||||
|
<String key="AboutTitle">About</String>
|
||||||
|
<String key="AboutForSimulator">for the MaSzyna simulator</String>
|
||||||
|
<String key="AboutChangelog">Changelog:</String>
|
||||||
|
<String key="AboutNoChangelog">No changelog file found.</String>
|
||||||
|
<String key="About">About</String>
|
||||||
|
<String key="Close">Close</String>
|
||||||
|
<String key="SettingsPath">Settings loaded from:</String>
|
||||||
|
|
||||||
|
<!-- Vehicle info -->
|
||||||
|
<String key="GeneralInfo">General</String>
|
||||||
|
<String key="Model">Model</String>
|
||||||
|
<String key="VMax">VMax</String>
|
||||||
|
<String key="Texture">Texture</String>
|
||||||
|
|
||||||
|
<!-- Scenery tab -->
|
||||||
|
<String key="SceneryConsists">Consists on the scenery</String>
|
||||||
|
<String key="SceneryFaults">Scenario faults:</String>
|
||||||
|
|
||||||
|
<!-- Warehouse / vehicle panels -->
|
||||||
|
<String key="WarehouseConsists">Consists in the warehouse</String>
|
||||||
|
<String key="AddToWarehouse">Add to warehouse</String>
|
||||||
|
<String key="RemoveFromWarehouse">Remove from warehouse</String>
|
||||||
|
<String key="WarehouseApply">Replace with warehouse consist</String>
|
||||||
|
<String key="WarehouseImportNothing">No consists found to import.</String>
|
||||||
|
<String key="AboutTexture">About texture</String>
|
||||||
|
<String key="NoTextureInfo">No texture information.</String>
|
||||||
|
|
||||||
|
<!-- Vehicle configuration / coupler -->
|
||||||
|
<String key="Configuration">Configuration</String>
|
||||||
|
<String key="RemoveVehicle">Remove vehicle</String>
|
||||||
|
<String key="AutoCoupler">Auto</String>
|
||||||
|
<String key="TipAutoCoupler">Pick couplings automatically for the whole consist</String>
|
||||||
|
<String key="CouplingLastVehicle">Rear coupler of the train — no vehicle behind it.</String>
|
||||||
|
<String key="ChangeName">Rename...</String>
|
||||||
|
<String key="LoadCopyToAll">Apply this load to every following vehicle that accepts it</String>
|
||||||
|
<String key="WarehouseAppend">Append warehouse consist</String>
|
||||||
|
<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="MassBrutto">Gross mass</String>
|
||||||
|
<String key="VisibilityGood">Good</String>
|
||||||
|
<String key="VisibilityBad">Poor</String>
|
||||||
|
<String key="SoundPhysics">Sound | Physics</String>
|
||||||
|
<String key="DriverHead">Driver (cab A)</String>
|
||||||
|
<String key="DriverRear">Driver (cab B)</String>
|
||||||
|
<String key="DriverPassenger">Passenger</String>
|
||||||
|
<String key="DriverNobody">Nobody</String>
|
||||||
|
<String key="RemoveAllTrains">Remove all consists from the scenery</String>
|
||||||
|
<String key="RemoveAllTrainsConfirm">Remove the vehicles from every consist on this scenery?</String>
|
||||||
|
<String key="TexWorks">Overhaul:</String>
|
||||||
|
<String key="RevisionFrom">Overhaul from:</String>
|
||||||
|
<String key="RevisionTo">Overhaul to:</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>
|
||||||
|
<String key="CatAll">All</String>
|
||||||
|
<String key="GfxColResolution">Resolution and textures</String>
|
||||||
|
<String key="GfxColRendering">Rendering</String>
|
||||||
|
<String key="GfxColShadows">Shadows</String>
|
||||||
|
<String key="GfxColReflections">Reflections and view</String>
|
||||||
|
<String key="GfxColScripts">Python scripts</String>
|
||||||
|
<String key="UartHint">Port name / leave alone / data send rate</String>
|
||||||
|
<String key="UartFeature">UARTfeature:</String>
|
||||||
|
<String key="UartSaveClose">Save and close</String>
|
||||||
|
<String key="PickCategoryFirst">Pick a vehicle category or class first.</String>
|
||||||
|
<String key="HideArchivalSceneries">Hide archived scenarios</String>
|
||||||
|
<String key="Tools">Tools</String>
|
||||||
|
<String key="AdvancedSettings">Advanced settings</String>
|
||||||
|
<String key="AdvLogPage">Event log</String>
|
||||||
|
<String key="AdvClearLog">Clear log</String>
|
||||||
|
<String key="ConsistSchedule">Composition</String>
|
||||||
</Language>
|
</Language>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
<String key="Time">Idő</String>
|
<String key="Time">Idő</String>
|
||||||
<String key="CurrentTime">Most</String>
|
<String key="CurrentTime">Most</String>
|
||||||
<String key="Day">Az év napja</String>
|
<String key="Day">Az év napja</String>
|
||||||
|
<String key="Date">Dátum</String>
|
||||||
|
<String key="DayOfYear">{0}. nap</String>
|
||||||
<String key="Sky">Égbolt</String>
|
<String key="Sky">Égbolt</String>
|
||||||
<String key="Fog">Köd / látótávolság</String>
|
<String key="Fog">Köd / látótávolság</String>
|
||||||
<String key="Season">Évszak</String>
|
<String key="Season">Évszak</String>
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
<String key="SeasonSummer">Nyár</String>
|
<String key="SeasonSummer">Nyár</String>
|
||||||
<String key="SeasonAutumn">Ősz</String>
|
<String key="SeasonAutumn">Ősz</String>
|
||||||
<String key="SeasonWinter">Tél</String>
|
<String key="SeasonWinter">Tél</String>
|
||||||
|
<String key="OvercastRandom">Véletlenszerű</String>
|
||||||
<String key="OvercastClear">Derült</String>
|
<String key="OvercastClear">Derült</String>
|
||||||
<String key="OvercastFair">Gyengén felhős</String>
|
<String key="OvercastFair">Gyengén felhős</String>
|
||||||
<String key="OvercastLight">Kevés felhő</String>
|
<String key="OvercastLight">Kevés felhő</String>
|
||||||
@@ -51,7 +54,8 @@
|
|||||||
<String key="OvercastHeavy">Erős felhőzet</String>
|
<String key="OvercastHeavy">Erős felhőzet</String>
|
||||||
<String key="OvercastCloudy">Borult</String>
|
<String key="OvercastCloudy">Borult</String>
|
||||||
<String key="OvercastStorm">Vihar</String>
|
<String key="OvercastStorm">Vihar</String>
|
||||||
<String key="WeatherHint">Az időjárás módosításai indításkor a pályába íródnak.</String>
|
<String key="WeatherDateTime">Dátum és idő</String>
|
||||||
|
<String key="WeatherConditions">Időjárási viszonyok</String>
|
||||||
<String key="Consist">Szerelvény</String>
|
<String key="Consist">Szerelvény</String>
|
||||||
<String key="Start">Indítás</String>
|
<String key="Start">Indítás</String>
|
||||||
<String key="StartNoScenery">Válasszon pályát</String>
|
<String key="StartNoScenery">Válasszon pályát</String>
|
||||||
@@ -148,40 +152,39 @@
|
|||||||
<String key="Coupling">Kapcsolókészülék</String>
|
<String key="Coupling">Kapcsolókészülék</String>
|
||||||
<String key="MapConsists">A pálya szerelvényei</String>
|
<String key="MapConsists">A pálya szerelvényei</String>
|
||||||
<String key="NoSceneryConsists">Válasszon pályát a Forgatókönyvek fülön.</String>
|
<String key="NoSceneryConsists">Válasszon pályát a Forgatókönyvek fülön.</String>
|
||||||
<String key="BrakeMode">Fékmód</String>
|
<String key="BrakeMode">Fékbeállítás</String>
|
||||||
<String key="BrakeLoad">Terhelési beállítás</String>
|
<String key="BrakeLoad">Terhelésbeállítás</String>
|
||||||
<String key="BrakeSwitch">Fékkapcsoló</String>
|
<String key="BrakeSwitch">Fékkapcsoló</String>
|
||||||
<String key="None">—</String>
|
<String key="None">—</String>
|
||||||
|
|
||||||
<!-- Coupling mask bits -->
|
<!-- Coupling mask bits -->
|
||||||
<String key="CplMechanical">Mechanikus</String>
|
<String key="CplMechanical">Mechanikus</String>
|
||||||
<String key="CplBrake">Pneumatikus 5 atm (fékek)</String>
|
<String key="CplBrake">Fék</String>
|
||||||
<String key="CplControl">Több egység vezérlése</String>
|
<String key="CplControl">Vezérlés</String>
|
||||||
<String key="CplHighVoltage">Nagyfeszültség</String>
|
<String key="CplHighVoltage">Nagyfeszültség</String>
|
||||||
<String key="CplGangway">Átjáró a járművek között</String>
|
<String key="CplGangway">Átjáró</String>
|
||||||
<String key="CplAuxPneumatic">Segéd pneumatikus 8 atm</String>
|
<String key="CplAuxPneumatic">Levegő 8atm</String>
|
||||||
<String key="CplHeating">Fűtés</String>
|
<String key="CplHeating">Fűtés</String>
|
||||||
<String key="CplWorkshop">Műhelykapcsolás (szétkapcsolás tiltása)</String>
|
<String key="CplWorkshop">Zár</String>
|
||||||
|
|
||||||
<!-- Brake modes -->
|
<!-- Brake modes -->
|
||||||
<String key="BrakeFreight">Teher (G)</String>
|
<String key="BrakeActingNone">Nincs</String>
|
||||||
<String key="BrakePassenger">Személy (P)</String>
|
<String key="BrakeFreight">G - teher</String>
|
||||||
<String key="BrakeExpress">Gyors (R)</String>
|
<String key="BrakePassenger">P - személy</String>
|
||||||
<String key="BrakeExpressMg">Gyors + mágneses sínfék (R+Mg)</String>
|
<String key="BrakeExpress">R - gyors</String>
|
||||||
<String key="BrakeNoAir">Indítás levegő nélkül (Q)</String>
|
<String key="BrakeExpressMg">R + mágneses sínfék</String>
|
||||||
<String key="BrakeOff">Fékek lekapcsolva (O)</String>
|
<String key="BrakeNoAir">Q - levegő nélkül</String>
|
||||||
<String key="BrakeAuto">Automatikus (A)</String>
|
|
||||||
|
|
||||||
<!-- Load adaptation -->
|
<!-- Load adaptation -->
|
||||||
<String key="LoadEmpty">Üres (T)</String>
|
<String key="LoadNeutral">N - semleges</String>
|
||||||
<String key="LoadMedium">Terhelt I — közepes (H)</String>
|
<String key="LoadEmpty">T - üres</String>
|
||||||
<String key="LoadFull">Terhelt II (F)</String>
|
<String key="LoadMedium">H - terhelt I</String>
|
||||||
<String key="LoadAuto">Automatikus (A)</String>
|
<String key="LoadFull">F - terhelt II</String>
|
||||||
|
|
||||||
<!-- Brake switch -->
|
<!-- Brake switch -->
|
||||||
<String key="SwitchOff">Ki (0)</String>
|
<String key="SwitchNormal">N - fék bekapcsolva</String>
|
||||||
<String key="SwitchOff10">Ki 10% eséllyel (1)</String>
|
<String key="SwitchOff">0 - kikapcsolva, oldva</String>
|
||||||
<String key="SwitchOn">Be (A)</String>
|
<String key="SwitchOff10">1 - kikapcsolva, oldva (10%)</String>
|
||||||
|
|
||||||
<!-- Damage (wheel parameters) -->
|
<!-- Damage (wheel parameters) -->
|
||||||
<String key="Damage">Sérülés</String>
|
<String key="Damage">Sérülés</String>
|
||||||
@@ -192,6 +195,9 @@
|
|||||||
<String key="LoadType">Rakomány típusa</String>
|
<String key="LoadType">Rakomány típusa</String>
|
||||||
<String key="LoadCount">Mennyiség</String>
|
<String key="LoadCount">Mennyiség</String>
|
||||||
<String key="LoadHint">A jármű megrakásához adjon meg 0-nál nagyobb mennyiséget.</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="WagonNumber">Kocsiszám</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="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="LoadMax">Legnagyobb lehetséges rakomány</String>
|
||||||
@@ -206,7 +212,7 @@
|
|||||||
<String key="VehicleCount">Járművek</String>
|
<String key="VehicleCount">Járművek</String>
|
||||||
<String key="Track">Vágány</String>
|
<String key="Track">Vágány</String>
|
||||||
<String key="EmptyTrainset">Nincs jármű. (Vágány: {0})</String>
|
<String key="EmptyTrainset">Nincs jármű. (Vágány: {0})</String>
|
||||||
<String key="HideArchivalVehicles">Archív járművek elrejtése</String>
|
<String key="HideArchivalVehicles">Archivált járművek elrejtése</String>
|
||||||
<String key="RemoveSelectedVehicle">Jármű eltávolítása</String>
|
<String key="RemoveSelectedVehicle">Jármű eltávolítása</String>
|
||||||
<String key="RandomizeTextures">Textúrák sorsolása</String>
|
<String key="RandomizeTextures">Textúrák sorsolása</String>
|
||||||
<String key="TipRandomizeTextures">Hasonló textúrák választása osztály / szabályok szerint</String>
|
<String key="TipRandomizeTextures">Hasonló textúrák választása osztály / szabályok szerint</String>
|
||||||
@@ -242,6 +248,10 @@
|
|||||||
<String key="ComingSoon">A szerkesztési lehetőségek hamarosan elérhetők.</String>
|
<String key="ComingSoon">A szerkesztési lehetőségek hamarosan elérhetők.</String>
|
||||||
<String key="Split">Szétbontás</String>
|
<String key="Split">Szétbontás</String>
|
||||||
<String key="TipSplit">Az egység szétbontása külön kocsikra</String>
|
<String key="TipSplit">Az egység szétbontása külön kocsikra</String>
|
||||||
|
<String key="AppliesToUnit">Az egész egységre közös</String>
|
||||||
|
<String key="CouplingUnitRear">Az egység végén lévő kapcsolókészülék</String>
|
||||||
|
<String key="Join">Összekapcsolás</String>
|
||||||
|
<String key="TipJoin">A jármű összekapcsolása a következővel egy egységgé</String>
|
||||||
<String key="AddVehicle">Hozzáadás</String>
|
<String key="AddVehicle">Hozzáadás</String>
|
||||||
<String key="TipAddVehicle">Jármű hozzáadása a szerelvényhez</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>
|
<String key="TipAddUnit">A teljes egység hozzáadása a szerelvényhez</String>
|
||||||
@@ -320,10 +330,28 @@
|
|||||||
<String key="MaxTexCabResolution">Vezetőállás-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="TexFilteringQuality">Textúraszűrés minősége:</String>
|
||||||
<String key="Multisampling">Multisampling:</String>
|
<String key="Multisampling">Multisampling:</String>
|
||||||
|
<String key="QualityVeryLow">Nagyon alacsony</String>
|
||||||
|
<String key="QualityLow">Alacsony</String>
|
||||||
|
<String key="QualityNormal">Normál</String>
|
||||||
|
<String key="QualityHigh">Magas</String>
|
||||||
|
<String key="QualityVeryHigh">Nagyon magas</String>
|
||||||
|
<String key="MsaaNone">Nincs</String>
|
||||||
|
<String key="QualityModerate">Közepes</String>
|
||||||
|
<String key="QualityStandard">Standard</String>
|
||||||
|
<String key="RangeVeryLow">Nagyon alacsony</String>
|
||||||
|
<String key="RangeLow">Alacsony</String>
|
||||||
|
<String key="RangeNormal">Normál</String>
|
||||||
|
<String key="RangeModerate">Közepes</String>
|
||||||
|
<String key="RangeStandard">Standard</String>
|
||||||
|
<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="DynamicLights">Dinamikus fényforrások:</String>
|
||||||
<String key="RenderRange">Megjelenítési távolság:</String>
|
<String key="RenderRange">Megjelenítési távolság:</String>
|
||||||
<String key="FpsLimitEnable">Képkockaszám korlátozása</String>
|
<String key="FpsLimitEnable">Képkockaszám korlátozása</String>
|
||||||
<String key="ShadowAngleLimit">Árnyékhossz korlátozása</String>
|
<String key="ShadowAngleLimit">Árnyékhossz korlátozása</String>
|
||||||
|
<String key="ShadowLimitNone">Nincs korlát</String>
|
||||||
|
<String key="ShadowLimitShort">Rövidített</String>
|
||||||
<String key="FullscreenWindowed">Automatikus felbontás (keret nélküli teljes képernyő)</String>
|
<String key="FullscreenWindowed">Automatikus felbontás (keret nélküli teljes képernyő)</String>
|
||||||
<String key="RenderAngleVulkan">ANGLE Vulkan megjelenítő használata</String>
|
<String key="RenderAngleVulkan">ANGLE Vulkan megjelenítő használata</String>
|
||||||
<String key="VSync">VSync</String>
|
<String key="VSync">VSync</String>
|
||||||
@@ -427,4 +455,100 @@
|
|||||||
<String key="Save">Mentés</String>
|
<String key="Save">Mentés</String>
|
||||||
<String key="Reset">Visszaállítás</String>
|
<String key="Reset">Visszaállítás</String>
|
||||||
<String key="SettingsSaved">A beállítások elmentve</String>
|
<String key="SettingsSaved">A beállítások elmentve</String>
|
||||||
|
|
||||||
|
<!-- Errors / diagnostics -->
|
||||||
|
<String key="FaultTitle">Hiba</String>
|
||||||
|
<String key="FaultDetail">Hiba részletei:</String>
|
||||||
|
<String key="FaultFileNotFound">A fájl nem található: {0}</String>
|
||||||
|
<String key="FaultLoadData">A szimulátor adatainak betöltése sikertelen.</String>
|
||||||
|
<String key="FaultBadInstall">A szimulátor telepítése hiányos lehet.</String>
|
||||||
|
<String key="FaultNoDir">Hiányzó mappa: {0}</String>
|
||||||
|
<String key="FaultNoWeights">Nincs rakománysúly-információ.</String>
|
||||||
|
<String key="FaultNoScenery">Nem található forgatókönyv.</String>
|
||||||
|
<String key="FaultNoVehicles">Nem található jármű.</String>
|
||||||
|
<String key="FaultNoPhysics">Nem található járműadat.</String>
|
||||||
|
<String key="FaultNoExe">A szimulátor futtatható fájlja nem található.</String>
|
||||||
|
|
||||||
|
<!-- About -->
|
||||||
|
<String key="AboutTitle">Névjegy</String>
|
||||||
|
<String key="AboutForSimulator">a MaSzyna szimulátorhoz</String>
|
||||||
|
<String key="AboutChangelog">Változásnapló:</String>
|
||||||
|
<String key="AboutNoChangelog">Nem található változásnapló fájl.</String>
|
||||||
|
<String key="About">Névjegy</String>
|
||||||
|
<String key="Close">Bezárás</String>
|
||||||
|
<String key="SettingsPath">Beállítások betöltve innen:</String>
|
||||||
|
|
||||||
|
<!-- Vehicle info -->
|
||||||
|
<String key="GeneralInfo">Általános</String>
|
||||||
|
<String key="Model">Modell</String>
|
||||||
|
<String key="VMax">VMax</String>
|
||||||
|
<String key="Texture">Textúra</String>
|
||||||
|
|
||||||
|
<!-- Scenery tab -->
|
||||||
|
<String key="SceneryConsists">Szerelvények a pályán</String>
|
||||||
|
<String key="SceneryFaults">Hibák a forgatókönyvben:</String>
|
||||||
|
|
||||||
|
<!-- Warehouse / vehicle panels -->
|
||||||
|
<String key="WarehouseConsists">Szerelvények a raktárban</String>
|
||||||
|
<String key="AddToWarehouse">Hozzáadás a raktárhoz</String>
|
||||||
|
<String key="RemoveFromWarehouse">Eltávolítás a raktárból</String>
|
||||||
|
<String key="WarehouseApply">Csere raktári szerelvényre</String>
|
||||||
|
<String key="WarehouseImportNothing">Nem található importálható szerelvény.</String>
|
||||||
|
<String key="AboutTexture">A textúráról</String>
|
||||||
|
<String key="NoTextureInfo">Nincs textúrainformáció.</String>
|
||||||
|
|
||||||
|
<!-- Vehicle configuration / coupler -->
|
||||||
|
<String key="Configuration">Konfiguráció</String>
|
||||||
|
<String key="RemoveVehicle">Jármű eltávolítása</String>
|
||||||
|
<String key="AutoCoupler">Auto</String>
|
||||||
|
<String key="TipAutoCoupler">Kapcsolatok automatikus beállítása az egész szerelvényre</String>
|
||||||
|
<String key="CouplingLastVehicle">A szerelvény végén lévő kapcsolókészülék — nincs mögötte jármű.</String>
|
||||||
|
<String key="ChangeName">Átnevezés...</String>
|
||||||
|
<String key="LoadCopyToAll">A rakomány alkalmazása minden további járműre, amely elfogadja</String>
|
||||||
|
<String key="WarehouseAppend">Raktári szerelvény hozzáfűzése</String>
|
||||||
|
<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="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="PyPriorityNormal">Normál</String>
|
||||||
|
<String key="PyPriorityLower">Alacsony</String>
|
||||||
|
<String key="PyPriorityLowest">Nagyon alacsony</String>
|
||||||
|
<String key="PyPriorityIdle">Nincs</String>
|
||||||
|
<String key="PyPriorityOff">Kikapcsolva</String>
|
||||||
|
|
||||||
|
<String key="MassBrutto">Bruttó tömeg</String>
|
||||||
|
<String key="VisibilityGood">Jó</String>
|
||||||
|
<String key="VisibilityBad">Rossz</String>
|
||||||
|
<String key="SoundPhysics">Hang | Fizika</String>
|
||||||
|
<String key="DriverHead">Mozdonyvezető (A fülke)</String>
|
||||||
|
<String key="DriverRear">Mozdonyvezető (B fülke)</String>
|
||||||
|
<String key="DriverPassenger">Utas</String>
|
||||||
|
<String key="DriverNobody">Senki</String>
|
||||||
|
<String key="RemoveAllTrains">Az összes szerelvény eltávolítása a pályáról</String>
|
||||||
|
<String key="RemoveAllTrainsConfirm">Eltávolítod a járműveket a pálya minden szerelvényéből?</String>
|
||||||
|
<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="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>
|
||||||
|
<String key="CatAll">Összes</String>
|
||||||
|
<String key="GfxColResolution">Felbontás és textúrák</String>
|
||||||
|
<String key="GfxColRendering">Megjelenítés</String>
|
||||||
|
<String key="GfxColShadows">Árnyékok</String>
|
||||||
|
<String key="GfxColReflections">Tükröződés és nézet</String>
|
||||||
|
<String key="GfxColScripts">Python szkriptek</String>
|
||||||
|
<String key="UartHint">Port neve / ne módosítsd / adatküldés gyakorisága</String>
|
||||||
|
<String key="UartFeature">UARTfeature:</String>
|
||||||
|
<String key="UartSaveClose">Mentés és bezárás</String>
|
||||||
|
<String key="PickCategoryFirst">Előbb válassz járműkategóriát vagy osztályt.</String>
|
||||||
|
<String key="HideArchivalSceneries">Archivált forgatókönyvek elrejtése</String>
|
||||||
|
<String key="Tools">Eszközök</String>
|
||||||
|
<String key="AdvancedSettings">Speciális beállítások</String>
|
||||||
|
<String key="AdvLogPage">Eseménynapló</String>
|
||||||
|
<String key="AdvClearLog">Napló törlése</String>
|
||||||
|
<String key="ConsistSchedule">Összeállítás</String>
|
||||||
</Language>
|
</Language>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
<String key="Time">Godzina</String>
|
<String key="Time">Godzina</String>
|
||||||
<String key="CurrentTime">Czas aktualny</String>
|
<String key="CurrentTime">Czas aktualny</String>
|
||||||
<String key="Day">Dzień roku</String>
|
<String key="Day">Dzień roku</String>
|
||||||
|
<String key="Date">Data</String>
|
||||||
|
<String key="DayOfYear">dzień {0}</String>
|
||||||
<String key="Sky">Niebo</String>
|
<String key="Sky">Niebo</String>
|
||||||
<String key="Fog">Mgła / widoczność</String>
|
<String key="Fog">Mgła / widoczność</String>
|
||||||
<String key="Season">Pora roku</String>
|
<String key="Season">Pora roku</String>
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
<String key="SeasonSummer">Lato</String>
|
<String key="SeasonSummer">Lato</String>
|
||||||
<String key="SeasonAutumn">Jesień</String>
|
<String key="SeasonAutumn">Jesień</String>
|
||||||
<String key="SeasonWinter">Zima</String>
|
<String key="SeasonWinter">Zima</String>
|
||||||
|
<String key="OvercastRandom">Losowe</String>
|
||||||
<String key="OvercastClear">Bezchmurnie</String>
|
<String key="OvercastClear">Bezchmurnie</String>
|
||||||
<String key="OvercastFair">Pogodnie</String>
|
<String key="OvercastFair">Pogodnie</String>
|
||||||
<String key="OvercastLight">Lekkie zachmurzenie</String>
|
<String key="OvercastLight">Lekkie zachmurzenie</String>
|
||||||
@@ -51,7 +54,8 @@
|
|||||||
<String key="OvercastHeavy">Duże zachmurzenie</String>
|
<String key="OvercastHeavy">Duże zachmurzenie</String>
|
||||||
<String key="OvercastCloudy">Pochmurno</String>
|
<String key="OvercastCloudy">Pochmurno</String>
|
||||||
<String key="OvercastStorm">Burza</String>
|
<String key="OvercastStorm">Burza</String>
|
||||||
<String key="WeatherHint">Zmiany pogody zostaną zapisane do scenerii przy uruchomieniu.</String>
|
<String key="WeatherDateTime">Data i godzina</String>
|
||||||
|
<String key="WeatherConditions">Warunki atmosferyczne</String>
|
||||||
<String key="Consist">Zestawienie</String>
|
<String key="Consist">Zestawienie</String>
|
||||||
<String key="Start">Uruchom</String>
|
<String key="Start">Uruchom</String>
|
||||||
<String key="StartNoScenery">Wybierz scenerię</String>
|
<String key="StartNoScenery">Wybierz scenerię</String>
|
||||||
@@ -148,40 +152,39 @@
|
|||||||
<String key="Coupling">Sprzęg</String>
|
<String key="Coupling">Sprzęg</String>
|
||||||
<String key="MapConsists">Składy na scenerii</String>
|
<String key="MapConsists">Składy na scenerii</String>
|
||||||
<String key="NoSceneryConsists">Wybierz scenerię w zakładce Scenariusze.</String>
|
<String key="NoSceneryConsists">Wybierz scenerię w zakładce Scenariusze.</String>
|
||||||
<String key="BrakeMode">Tryb hamulca</String>
|
<String key="BrakeMode">Nastawa</String>
|
||||||
<String key="BrakeLoad">Nastawa ładunkowa</String>
|
<String key="BrakeLoad">Dostosowanie</String>
|
||||||
<String key="BrakeSwitch">Włącznik hamulca</String>
|
<String key="BrakeSwitch">Wyłącznik</String>
|
||||||
<String key="None">—</String>
|
<String key="None">—</String>
|
||||||
|
|
||||||
<!-- Bity maski sprzęgu -->
|
<!-- Bity maski sprzęgu -->
|
||||||
<String key="CplMechanical">Mechaniczny</String>
|
<String key="CplMechanical">Mechanicznie</String>
|
||||||
<String key="CplBrake">Pneumatyczny 5 atm (hamulce)</String>
|
<String key="CplBrake">Hamulec</String>
|
||||||
<String key="CplControl">Ukrotnienie (sterowanie wielokrotne)</String>
|
<String key="CplControl">Ukrotnienie</String>
|
||||||
<String key="CplHighVoltage">Wysokie napięcie</String>
|
<String key="CplHighVoltage">Wysokie napięcie</String>
|
||||||
<String key="CplGangway">Przejście między pojazdami</String>
|
<String key="CplGangway">Przejście</String>
|
||||||
<String key="CplAuxPneumatic">Pneumatyczny pomocniczy 8 atm</String>
|
<String key="CplAuxPneumatic">Powietrze 8atm</String>
|
||||||
<String key="CplHeating">Ogrzewanie</String>
|
<String key="CplHeating">Ogrzewanie</String>
|
||||||
<String key="CplWorkshop">Sprzęg warsztatowy (blokada rozłączania)</String>
|
<String key="CplWorkshop">Blokada</String>
|
||||||
|
|
||||||
<!-- Tryby nastawy hamulca -->
|
<!-- Tryby nastawy hamulca -->
|
||||||
<String key="BrakeFreight">Towarowa (G)</String>
|
<String key="BrakeActingNone">Brak</String>
|
||||||
<String key="BrakePassenger">Osobowa (P)</String>
|
<String key="BrakeFreight">G - nastawa towarowa</String>
|
||||||
<String key="BrakeExpress">Pospieszna (R)</String>
|
<String key="BrakePassenger">P - nastawa pasażerska</String>
|
||||||
<String key="BrakeExpressMg">Pospieszna + szynowy magnetyczny (R+Mg)</String>
|
<String key="BrakeExpress">R - nastawa pospieszna</String>
|
||||||
<String key="BrakeNoAir">Start bez powietrza (Q)</String>
|
<String key="BrakeExpressMg">R + magn. ham. szynowy</String>
|
||||||
<String key="BrakeOff">Odłączone hamulce (O)</String>
|
<String key="BrakeNoAir">Q - bez powietrza</String>
|
||||||
<String key="BrakeAuto">Wybór automatyczny (A)</String>
|
|
||||||
|
|
||||||
<!-- Nastawa ładunkowa -->
|
<!-- Dostosowanie do ładunku -->
|
||||||
<String key="LoadEmpty">Próżny (T)</String>
|
<String key="LoadNeutral">N - nastawienie neutralne</String>
|
||||||
<String key="LoadMedium">Ładowny I — średni (H)</String>
|
<String key="LoadEmpty">T - próżny</String>
|
||||||
<String key="LoadFull">Ładowny II (F)</String>
|
<String key="LoadMedium">H - ładowny I</String>
|
||||||
<String key="LoadAuto">Wybór automatyczny (A)</String>
|
<String key="LoadFull">F - ładowny II</String>
|
||||||
|
|
||||||
<!-- Włącznik hamulca -->
|
<!-- Wyłącznik hamulca -->
|
||||||
<String key="SwitchOff">Wyłączony (0)</String>
|
<String key="SwitchNormal">N - hamulec włączony</String>
|
||||||
<String key="SwitchOff10">Wyłączony z prawd. 10% (1)</String>
|
<String key="SwitchOff">0 - wyłączony, odluźniony</String>
|
||||||
<String key="SwitchOn">Włączony (A)</String>
|
<String key="SwitchOff10">1 - wył., odl. z 10% szansą</String>
|
||||||
|
|
||||||
<!-- Uszkodzenia (parametry kół) -->
|
<!-- Uszkodzenia (parametry kół) -->
|
||||||
<String key="Damage">Uszkodzenia</String>
|
<String key="Damage">Uszkodzenia</String>
|
||||||
@@ -192,6 +195,9 @@
|
|||||||
<String key="LoadType">Rodzaj ładunku</String>
|
<String key="LoadType">Rodzaj ładunku</String>
|
||||||
<String key="LoadCount">Ilość</String>
|
<String key="LoadCount">Ilość</String>
|
||||||
<String key="LoadHint">Ustaw ilość większą od 0, aby załadować pojazd.</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="WagonNumber">Numer wagonu</String>
|
<String key="WagonNumber">Numer wagonu</String>
|
||||||
<String key="LoadCopyPrev">Kopiuj ładunek z poprzedniego pojazdu</String>
|
<String key="LoadCopyPrev">Kopiuj ładunek z poprzedniego pojazdu</String>
|
||||||
<String key="LoadMax">Maksymalny możliwy ładunek</String>
|
<String key="LoadMax">Maksymalny możliwy ładunek</String>
|
||||||
@@ -206,7 +212,7 @@
|
|||||||
<String key="VehicleCount">Pojazdy</String>
|
<String key="VehicleCount">Pojazdy</String>
|
||||||
<String key="Track">Tor</String>
|
<String key="Track">Tor</String>
|
||||||
<String key="EmptyTrainset">Wpis bez pojazdów. (Tor: {0})</String>
|
<String key="EmptyTrainset">Wpis bez pojazdów. (Tor: {0})</String>
|
||||||
<String key="HideArchivalVehicles">Ukryj pojazdy archiwalne</String>
|
<String key="HideArchivalVehicles">Ukryj archiwalne pojazdy</String>
|
||||||
<String key="RemoveSelectedVehicle">Usuń pojazd</String>
|
<String key="RemoveSelectedVehicle">Usuń pojazd</String>
|
||||||
<String key="RandomizeTextures">Losuj tekstury</String>
|
<String key="RandomizeTextures">Losuj tekstury</String>
|
||||||
<String key="TipRandomizeTextures">Dobierz podobne tekstury według klasy / reguł</String>
|
<String key="TipRandomizeTextures">Dobierz podobne tekstury według klasy / reguł</String>
|
||||||
@@ -242,6 +248,10 @@
|
|||||||
<String key="ComingSoon">Opcje edycji wkrótce.</String>
|
<String key="ComingSoon">Opcje edycji wkrótce.</String>
|
||||||
<String key="Split">Rozdziel</String>
|
<String key="Split">Rozdziel</String>
|
||||||
<String key="TipSplit">Rozdziel zespół na pojedyncze człony</String>
|
<String key="TipSplit">Rozdziel zespół na pojedyncze człony</String>
|
||||||
|
<String key="AppliesToUnit">Ustawienie wspólne dla całego zespołu</String>
|
||||||
|
<String key="CouplingUnitRear">Sprzęg na końcu zespołu</String>
|
||||||
|
<String key="Join">Złącz</String>
|
||||||
|
<String key="TipJoin">Połącz ten pojazd z następnym w jeden zespół</String>
|
||||||
<String key="AddVehicle">Dodaj</String>
|
<String key="AddVehicle">Dodaj</String>
|
||||||
<String key="TipAddVehicle">Dodaj ten pojazd do składu</String>
|
<String key="TipAddVehicle">Dodaj ten pojazd do składu</String>
|
||||||
<String key="TipAddUnit">Dodaj cały zespół do składu</String>
|
<String key="TipAddUnit">Dodaj cały zespół do składu</String>
|
||||||
@@ -320,10 +330,28 @@
|
|||||||
<String key="MaxTexCabResolution">Maksymalna rozdzielczość tekstur kabiny:</String>
|
<String key="MaxTexCabResolution">Maksymalna rozdzielczość tekstur kabiny:</String>
|
||||||
<String key="TexFilteringQuality">Jakość filtrowania tekstur:</String>
|
<String key="TexFilteringQuality">Jakość filtrowania tekstur:</String>
|
||||||
<String key="Multisampling">Multisampling:</String>
|
<String key="Multisampling">Multisampling:</String>
|
||||||
|
<String key="QualityVeryLow">Bardzo niska</String>
|
||||||
|
<String key="QualityLow">Niska</String>
|
||||||
|
<String key="QualityNormal">Normalna</String>
|
||||||
|
<String key="QualityHigh">Wysoka</String>
|
||||||
|
<String key="QualityVeryHigh">Bardzo wysoka</String>
|
||||||
|
<String key="MsaaNone">Brak</String>
|
||||||
|
<String key="QualityModerate">Umiarkowana</String>
|
||||||
|
<String key="QualityStandard">Standardowa</String>
|
||||||
|
<String key="RangeVeryLow">Bardzo niski</String>
|
||||||
|
<String key="RangeLow">Niski</String>
|
||||||
|
<String key="RangeNormal">Normalny</String>
|
||||||
|
<String key="RangeModerate">Umiarkowany</String>
|
||||||
|
<String key="RangeStandard">Standardowy</String>
|
||||||
|
<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="DynamicLights">Dynamiczne źródła światła:</String>
|
||||||
<String key="RenderRange">Zasięg renderowania:</String>
|
<String key="RenderRange">Zasięg renderowania:</String>
|
||||||
<String key="FpsLimitEnable">Ogranicz liczbę klatek na sekundę</String>
|
<String key="FpsLimitEnable">Ogranicz liczbę klatek na sekundę</String>
|
||||||
<String key="ShadowAngleLimit">Limit długości cienia</String>
|
<String key="ShadowAngleLimit">Limit długości cienia</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="FullscreenWindowed">Automatyczna rozdzielczość (pełny ekran bez obramowania)</String>
|
||||||
<String key="RenderAngleVulkan">Użyj renderera ANGLE Vulkan</String>
|
<String key="RenderAngleVulkan">Użyj renderera ANGLE Vulkan</String>
|
||||||
<String key="VSync">VSync</String>
|
<String key="VSync">VSync</String>
|
||||||
@@ -427,4 +455,100 @@
|
|||||||
<String key="Save">Zapisz</String>
|
<String key="Save">Zapisz</String>
|
||||||
<String key="Reset">Przywróć</String>
|
<String key="Reset">Przywróć</String>
|
||||||
<String key="SettingsSaved">Zapisano ustawienia</String>
|
<String key="SettingsSaved">Zapisano ustawienia</String>
|
||||||
|
|
||||||
|
<!-- Errors / diagnostics -->
|
||||||
|
<String key="FaultTitle">Błąd</String>
|
||||||
|
<String key="FaultDetail">Szczegóły błędu:</String>
|
||||||
|
<String key="FaultFileNotFound">Nie znaleziono pliku: {0}</String>
|
||||||
|
<String key="FaultLoadData">Błąd wczytywania danych symulatora.</String>
|
||||||
|
<String key="FaultBadInstall">Możliwa błędna instalacja symulatora.</String>
|
||||||
|
<String key="FaultNoDir">Brak katalogu {0}</String>
|
||||||
|
<String key="FaultNoWeights">Brak informacji o wagach ładunków.</String>
|
||||||
|
<String key="FaultNoScenery">Nie znaleziono scenariuszy.</String>
|
||||||
|
<String key="FaultNoVehicles">Nie znaleziono pojazdów.</String>
|
||||||
|
<String key="FaultNoPhysics">Nie znaleziono danych taboru.</String>
|
||||||
|
<String key="FaultNoExe">Nie znaleziono pliku wykonywalnego symulatora.</String>
|
||||||
|
|
||||||
|
<!-- About -->
|
||||||
|
<String key="AboutTitle">O programie</String>
|
||||||
|
<String key="AboutForSimulator">dla symulatora MaSzyna</String>
|
||||||
|
<String key="AboutChangelog">Lista zmian:</String>
|
||||||
|
<String key="AboutNoChangelog">Brak pliku z listą zmian.</String>
|
||||||
|
<String key="About">O programie</String>
|
||||||
|
<String key="Close">Zamknij</String>
|
||||||
|
<String key="SettingsPath">Lokalizacja wczytanych ustawień:</String>
|
||||||
|
|
||||||
|
<!-- Vehicle info -->
|
||||||
|
<String key="GeneralInfo">Ogólne</String>
|
||||||
|
<String key="Model">Model</String>
|
||||||
|
<String key="VMax">VMax</String>
|
||||||
|
<String key="Texture">Tekstura</String>
|
||||||
|
|
||||||
|
<!-- Scenery tab -->
|
||||||
|
<String key="SceneryConsists">Składy na scenerii</String>
|
||||||
|
<String key="SceneryFaults">Błędy w scenariuszu:</String>
|
||||||
|
|
||||||
|
<!-- Warehouse / vehicle panels -->
|
||||||
|
<String key="WarehouseConsists">Składy w magazynie</String>
|
||||||
|
<String key="AddToWarehouse">Dodaj do magazynu</String>
|
||||||
|
<String key="RemoveFromWarehouse">Usuń z magazynu</String>
|
||||||
|
<String key="WarehouseApply">Zastąp składem z magazynu</String>
|
||||||
|
<String key="WarehouseImportNothing">Nie znaleziono składów do zaimportowania.</String>
|
||||||
|
<String key="AboutTexture">O teksturze</String>
|
||||||
|
<String key="NoTextureInfo">Brak informacji o teksturze.</String>
|
||||||
|
|
||||||
|
<!-- Vehicle configuration / coupler -->
|
||||||
|
<String key="Configuration">Konfiguracja</String>
|
||||||
|
<String key="RemoveVehicle">Usuń pojazd</String>
|
||||||
|
<String key="AutoCoupler">Auto</String>
|
||||||
|
<String key="TipAutoCoupler">Dobierz sprzęgi automatycznie dla całego składu</String>
|
||||||
|
<String key="CouplingLastVehicle">Sprzęg na końcu składu — nie ma za nim pojazdu.</String>
|
||||||
|
<String key="ChangeName">Zmień nazwę...</String>
|
||||||
|
<String key="LoadCopyToAll">Dodaj ten ładunek do wszystkich możliwych następnych pojazdów</String>
|
||||||
|
<String key="WarehouseAppend">Dołącz skład z magazynu</String>
|
||||||
|
<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="PyPriorityOff">Wyłączona</String>
|
||||||
|
|
||||||
|
<String key="MassBrutto">Masa brutto</String>
|
||||||
|
<String key="VisibilityGood">Dobra</String>
|
||||||
|
<String key="VisibilityBad">Zła</String>
|
||||||
|
<String key="SoundPhysics">Dźwięk | Fizyka</String>
|
||||||
|
<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="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="RuleAdd">Dodaj do reguły</String>
|
||||||
|
<String key="RuleNew">— nowa reguła —</String>
|
||||||
|
<String key="RuleRemove">Usuń z reguły</String>
|
||||||
|
<String key="CatAll">Wszystkie</String>
|
||||||
|
<String key="GfxColResolution">Rozdzielczość i tekstury</String>
|
||||||
|
<String key="GfxColRendering">Renderowanie</String>
|
||||||
|
<String key="GfxColShadows">Cienie</String>
|
||||||
|
<String key="GfxColReflections">Odbicia i widok</String>
|
||||||
|
<String key="GfxColScripts">Skrypty Python</String>
|
||||||
|
<String key="UartHint">Nazwa portu / nie zmieniać / częstotliwość wysyłania danych</String>
|
||||||
|
<String key="UartFeature">UARTfeature:</String>
|
||||||
|
<String key="UartSaveClose">Zapisz i zamknij</String>
|
||||||
|
<String key="PickCategoryFirst">Najpierw wybierz kategorię lub klasę pojazdów.</String>
|
||||||
|
<String key="HideArchivalSceneries">Ukryj scenariusze archiwalne</String>
|
||||||
|
<String key="Tools">Narzędzia</String>
|
||||||
|
<String key="AdvancedSettings">Ustawienia zaawansowane</String>
|
||||||
|
<String key="AdvLogPage">Dziennik zdarzeń</String>
|
||||||
|
<String key="AdvClearLog">Wyczyść log</String>
|
||||||
|
<String key="ConsistSchedule">Zestawienie</String>
|
||||||
</Language>
|
</Language>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
<String key="Time">Время</String>
|
<String key="Time">Время</String>
|
||||||
<String key="CurrentTime">Сейчас</String>
|
<String key="CurrentTime">Сейчас</String>
|
||||||
<String key="Day">День года</String>
|
<String key="Day">День года</String>
|
||||||
|
<String key="Date">Дата</String>
|
||||||
|
<String key="DayOfYear">день {0}</String>
|
||||||
<String key="Sky">Небо</String>
|
<String key="Sky">Небо</String>
|
||||||
<String key="Fog">Туман / видимость</String>
|
<String key="Fog">Туман / видимость</String>
|
||||||
<String key="Season">Время года</String>
|
<String key="Season">Время года</String>
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
<String key="SeasonSummer">Лето</String>
|
<String key="SeasonSummer">Лето</String>
|
||||||
<String key="SeasonAutumn">Осень</String>
|
<String key="SeasonAutumn">Осень</String>
|
||||||
<String key="SeasonWinter">Зима</String>
|
<String key="SeasonWinter">Зима</String>
|
||||||
|
<String key="OvercastRandom">Случайно</String>
|
||||||
<String key="OvercastClear">Ясно</String>
|
<String key="OvercastClear">Ясно</String>
|
||||||
<String key="OvercastFair">Малооблачно</String>
|
<String key="OvercastFair">Малооблачно</String>
|
||||||
<String key="OvercastLight">Лёгкая облачность</String>
|
<String key="OvercastLight">Лёгкая облачность</String>
|
||||||
@@ -51,7 +54,8 @@
|
|||||||
<String key="OvercastHeavy">Сильная облачность</String>
|
<String key="OvercastHeavy">Сильная облачность</String>
|
||||||
<String key="OvercastCloudy">Пасмурно</String>
|
<String key="OvercastCloudy">Пасмурно</String>
|
||||||
<String key="OvercastStorm">Гроза</String>
|
<String key="OvercastStorm">Гроза</String>
|
||||||
<String key="WeatherHint">Изменения погоды записываются в сценерию при запуске.</String>
|
<String key="WeatherDateTime">Дата и время</String>
|
||||||
|
<String key="WeatherConditions">Погодные условия</String>
|
||||||
<String key="Consist">Состав</String>
|
<String key="Consist">Состав</String>
|
||||||
<String key="Start">Пуск</String>
|
<String key="Start">Пуск</String>
|
||||||
<String key="StartNoScenery">Выберите сценерию</String>
|
<String key="StartNoScenery">Выберите сценерию</String>
|
||||||
@@ -148,40 +152,39 @@
|
|||||||
<String key="Coupling">Сцепка</String>
|
<String key="Coupling">Сцепка</String>
|
||||||
<String key="MapConsists">Составы на сценерии</String>
|
<String key="MapConsists">Составы на сценерии</String>
|
||||||
<String key="NoSceneryConsists">Выберите сценерию на вкладке Сценарии.</String>
|
<String key="NoSceneryConsists">Выберите сценерию на вкладке Сценарии.</String>
|
||||||
<String key="BrakeMode">Режим торможения</String>
|
<String key="BrakeMode">Установка тормоза</String>
|
||||||
<String key="BrakeLoad">Режим загрузки</String>
|
<String key="BrakeLoad">Регулировка тормоза</String>
|
||||||
<String key="BrakeSwitch">Переключатель тормоза</String>
|
<String key="BrakeSwitch">Выключатель тормоза</String>
|
||||||
<String key="None">—</String>
|
<String key="None">—</String>
|
||||||
|
|
||||||
<!-- Coupling mask bits -->
|
<!-- Coupling mask bits -->
|
||||||
<String key="CplMechanical">Механическая</String>
|
<String key="CplMechanical">Механическая</String>
|
||||||
<String key="CplBrake">Пневматическая 5 атм (тормоза)</String>
|
<String key="CplBrake">Тормоз</String>
|
||||||
<String key="CplControl">Управление по системе многих единиц</String>
|
<String key="CplControl">Управление</String>
|
||||||
<String key="CplHighVoltage">Высокое напряжение</String>
|
<String key="CplHighVoltage">Высокое напряжение</String>
|
||||||
<String key="CplGangway">Переход между единицами</String>
|
<String key="CplGangway">Переход</String>
|
||||||
<String key="CplAuxPneumatic">Вспомогательная пневматическая 8 атм</String>
|
<String key="CplAuxPneumatic">Воздух 8атм</String>
|
||||||
<String key="CplHeating">Отопление</String>
|
<String key="CplHeating">Отопление</String>
|
||||||
<String key="CplWorkshop">Депотская сцепка (блокировка расцепления)</String>
|
<String key="CplWorkshop">Блокировка</String>
|
||||||
|
|
||||||
<!-- Brake modes -->
|
<!-- Brake modes -->
|
||||||
<String key="BrakeFreight">Грузовой (G)</String>
|
<String key="BrakeActingNone">Нет</String>
|
||||||
<String key="BrakePassenger">Пассажирский (P)</String>
|
<String key="BrakeFreight">G - грузовой режим</String>
|
||||||
<String key="BrakeExpress">Скорый (R)</String>
|
<String key="BrakePassenger">P - пассажирский режим</String>
|
||||||
<String key="BrakeExpressMg">Скорый + магниторельсовый (R+Mg)</String>
|
<String key="BrakeExpress">R - скорый режим</String>
|
||||||
<String key="BrakeNoAir">Пуск без воздуха (Q)</String>
|
<String key="BrakeExpressMg">R + магниторельсовый тормоз</String>
|
||||||
<String key="BrakeOff">Тормоза отключены (O)</String>
|
<String key="BrakeNoAir">Q - без воздуха</String>
|
||||||
<String key="BrakeAuto">Автоматически (A)</String>
|
|
||||||
|
|
||||||
<!-- Load adaptation -->
|
<!-- Load adaptation -->
|
||||||
<String key="LoadEmpty">Порожний (T)</String>
|
<String key="LoadNeutral">N - нейтральное положение</String>
|
||||||
<String key="LoadMedium">Гружёный I — средний (H)</String>
|
<String key="LoadEmpty">T - порожний</String>
|
||||||
<String key="LoadFull">Гружёный II (F)</String>
|
<String key="LoadMedium">H - гружёный I</String>
|
||||||
<String key="LoadAuto">Автоматически (A)</String>
|
<String key="LoadFull">F - гружёный II</String>
|
||||||
|
|
||||||
<!-- Brake switch -->
|
<!-- Brake switch -->
|
||||||
<String key="SwitchOff">Выкл. (0)</String>
|
<String key="SwitchNormal">N - тормоз включён</String>
|
||||||
<String key="SwitchOff10">Выкл. с вероятностью 10 % (1)</String>
|
<String key="SwitchOff">0 - выключен, отпущен</String>
|
||||||
<String key="SwitchOn">Вкл. (A)</String>
|
<String key="SwitchOff10">1 - выключен, отпущен (10%)</String>
|
||||||
|
|
||||||
<!-- Damage (wheel parameters) -->
|
<!-- Damage (wheel parameters) -->
|
||||||
<String key="Damage">Повреждения</String>
|
<String key="Damage">Повреждения</String>
|
||||||
@@ -192,6 +195,9 @@
|
|||||||
<String key="LoadType">Вид груза</String>
|
<String key="LoadType">Вид груза</String>
|
||||||
<String key="LoadCount">Количество</String>
|
<String key="LoadCount">Количество</String>
|
||||||
<String key="LoadHint">Задайте количество больше 0, чтобы загрузить единицу.</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="WagonNumber">Номер вагона</String>
|
<String key="WagonNumber">Номер вагона</String>
|
||||||
<String key="LoadCopyPrev">Копировать груз с предыдущей единицы</String>
|
<String key="LoadCopyPrev">Копировать груз с предыдущей единицы</String>
|
||||||
<String key="LoadMax">Максимально возможный груз</String>
|
<String key="LoadMax">Максимально возможный груз</String>
|
||||||
@@ -206,7 +212,7 @@
|
|||||||
<String key="VehicleCount">Единиц</String>
|
<String key="VehicleCount">Единиц</String>
|
||||||
<String key="Track">Путь</String>
|
<String key="Track">Путь</String>
|
||||||
<String key="EmptyTrainset">Нет единиц. (Путь: {0})</String>
|
<String key="EmptyTrainset">Нет единиц. (Путь: {0})</String>
|
||||||
<String key="HideArchivalVehicles">Скрыть архивные единицы</String>
|
<String key="HideArchivalVehicles">Скрыть архивный транспорт</String>
|
||||||
<String key="RemoveSelectedVehicle">Убрать единицу</String>
|
<String key="RemoveSelectedVehicle">Убрать единицу</String>
|
||||||
<String key="RandomizeTextures">Случайные текстуры</String>
|
<String key="RandomizeTextures">Случайные текстуры</String>
|
||||||
<String key="TipRandomizeTextures">Подобрать похожие текстуры по классу / правилам</String>
|
<String key="TipRandomizeTextures">Подобрать похожие текстуры по классу / правилам</String>
|
||||||
@@ -242,6 +248,10 @@
|
|||||||
<String key="ComingSoon">Параметры редактирования появятся позже.</String>
|
<String key="ComingSoon">Параметры редактирования появятся позже.</String>
|
||||||
<String key="Split">Разделить</String>
|
<String key="Split">Разделить</String>
|
||||||
<String key="TipSplit">Разделить сцеп на отдельные вагоны</String>
|
<String key="TipSplit">Разделить сцеп на отдельные вагоны</String>
|
||||||
|
<String key="AppliesToUnit">Общее для всего состава</String>
|
||||||
|
<String key="CouplingUnitRear">Сцепка в конце состава</String>
|
||||||
|
<String key="Join">Соединить</String>
|
||||||
|
<String key="TipJoin">Соединить этот вагон со следующим в один состав</String>
|
||||||
<String key="AddVehicle">Добавить</String>
|
<String key="AddVehicle">Добавить</String>
|
||||||
<String key="TipAddVehicle">Добавить эту единицу в состав</String>
|
<String key="TipAddVehicle">Добавить эту единицу в состав</String>
|
||||||
<String key="TipAddUnit">Добавить весь сцеп в состав</String>
|
<String key="TipAddUnit">Добавить весь сцеп в состав</String>
|
||||||
@@ -320,10 +330,28 @@
|
|||||||
<String key="MaxTexCabResolution">Максимальное разрешение текстур кабины:</String>
|
<String key="MaxTexCabResolution">Максимальное разрешение текстур кабины:</String>
|
||||||
<String key="TexFilteringQuality">Качество фильтрации текстур:</String>
|
<String key="TexFilteringQuality">Качество фильтрации текстур:</String>
|
||||||
<String key="Multisampling">Multisampling:</String>
|
<String key="Multisampling">Multisampling:</String>
|
||||||
|
<String key="QualityVeryLow">Очень низкое</String>
|
||||||
|
<String key="QualityLow">Низкое</String>
|
||||||
|
<String key="QualityNormal">Нормальное</String>
|
||||||
|
<String key="QualityHigh">Высокое</String>
|
||||||
|
<String key="QualityVeryHigh">Очень высокое</String>
|
||||||
|
<String key="MsaaNone">Нет</String>
|
||||||
|
<String key="QualityModerate">Умеренное</String>
|
||||||
|
<String key="QualityStandard">Стандартное</String>
|
||||||
|
<String key="RangeVeryLow">Очень низкий</String>
|
||||||
|
<String key="RangeLow">Низкий</String>
|
||||||
|
<String key="RangeNormal">Нормальный</String>
|
||||||
|
<String key="RangeModerate">Умеренный</String>
|
||||||
|
<String key="RangeStandard">Стандартный</String>
|
||||||
|
<String key="RangeHigh">Высокий</String>
|
||||||
|
<String key="RangeVeryHigh">Очень высокий</String>
|
||||||
|
<String key="RangeDisabled">Отключено</String>
|
||||||
<String key="DynamicLights">Динамические источники света:</String>
|
<String key="DynamicLights">Динамические источники света:</String>
|
||||||
<String key="RenderRange">Дальность отрисовки:</String>
|
<String key="RenderRange">Дальность отрисовки:</String>
|
||||||
<String key="FpsLimitEnable">Ограничить частоту кадров</String>
|
<String key="FpsLimitEnable">Ограничить частоту кадров</String>
|
||||||
<String key="ShadowAngleLimit">Ограничение длины теней</String>
|
<String key="ShadowAngleLimit">Ограничение длины теней</String>
|
||||||
|
<String key="ShadowLimitNone">Без ограничений</String>
|
||||||
|
<String key="ShadowLimitShort">Укороченные</String>
|
||||||
<String key="FullscreenWindowed">Автоматическое разрешение (полный экран без рамки)</String>
|
<String key="FullscreenWindowed">Автоматическое разрешение (полный экран без рамки)</String>
|
||||||
<String key="RenderAngleVulkan">Использовать рендерер ANGLE Vulkan</String>
|
<String key="RenderAngleVulkan">Использовать рендерер ANGLE Vulkan</String>
|
||||||
<String key="VSync">VSync</String>
|
<String key="VSync">VSync</String>
|
||||||
@@ -427,4 +455,100 @@
|
|||||||
<String key="Save">Сохранить</String>
|
<String key="Save">Сохранить</String>
|
||||||
<String key="Reset">Сбросить</String>
|
<String key="Reset">Сбросить</String>
|
||||||
<String key="SettingsSaved">Настройки сохранены</String>
|
<String key="SettingsSaved">Настройки сохранены</String>
|
||||||
|
|
||||||
|
<!-- Errors / diagnostics -->
|
||||||
|
<String key="FaultTitle">Ошибка</String>
|
||||||
|
<String key="FaultDetail">Подробности ошибки:</String>
|
||||||
|
<String key="FaultFileNotFound">Файл не найден: {0}</String>
|
||||||
|
<String key="FaultLoadData">Не удалось загрузить данные симулятора.</String>
|
||||||
|
<String key="FaultBadInstall">Возможно, симулятор установлен некорректно.</String>
|
||||||
|
<String key="FaultNoDir">Отсутствует папка {0}</String>
|
||||||
|
<String key="FaultNoWeights">Нет данных о массе грузов.</String>
|
||||||
|
<String key="FaultNoScenery">Сценарии не найдены.</String>
|
||||||
|
<String key="FaultNoVehicles">Транспортные средства не найдены.</String>
|
||||||
|
<String key="FaultNoPhysics">Данные подвижного состава не найдены.</String>
|
||||||
|
<String key="FaultNoExe">Исполняемый файл симулятора не найден.</String>
|
||||||
|
|
||||||
|
<!-- About -->
|
||||||
|
<String key="AboutTitle">О программе</String>
|
||||||
|
<String key="AboutForSimulator">для симулятора MaSzyna</String>
|
||||||
|
<String key="AboutChangelog">Список изменений:</String>
|
||||||
|
<String key="AboutNoChangelog">Файл со списком изменений не найден.</String>
|
||||||
|
<String key="About">О программе</String>
|
||||||
|
<String key="Close">Закрыть</String>
|
||||||
|
<String key="SettingsPath">Настройки загружены из:</String>
|
||||||
|
|
||||||
|
<!-- Vehicle info -->
|
||||||
|
<String key="GeneralInfo">Общие</String>
|
||||||
|
<String key="Model">Модель</String>
|
||||||
|
<String key="VMax">VMax</String>
|
||||||
|
<String key="Texture">Текстура</String>
|
||||||
|
|
||||||
|
<!-- Scenery tab -->
|
||||||
|
<String key="SceneryConsists">Составы на сценерии</String>
|
||||||
|
<String key="SceneryFaults">Ошибки в сценарии:</String>
|
||||||
|
|
||||||
|
<!-- Warehouse / vehicle panels -->
|
||||||
|
<String key="WarehouseConsists">Составы на складе</String>
|
||||||
|
<String key="AddToWarehouse">Добавить на склад</String>
|
||||||
|
<String key="RemoveFromWarehouse">Удалить со склада</String>
|
||||||
|
<String key="WarehouseApply">Заменить составом со склада</String>
|
||||||
|
<String key="WarehouseImportNothing">Составы для импорта не найдены.</String>
|
||||||
|
<String key="AboutTexture">О текстуре</String>
|
||||||
|
<String key="NoTextureInfo">Нет информации о текстуре.</String>
|
||||||
|
|
||||||
|
<!-- Vehicle configuration / coupler -->
|
||||||
|
<String key="Configuration">Конфигурация</String>
|
||||||
|
<String key="RemoveVehicle">Удалить транспорт</String>
|
||||||
|
<String key="AutoCoupler">Авто</String>
|
||||||
|
<String key="TipAutoCoupler">Подобрать сцепки автоматически для всего состава</String>
|
||||||
|
<String key="CouplingLastVehicle">Сцепка в конце состава — за ней нет вагона.</String>
|
||||||
|
<String key="ChangeName">Переименовать...</String>
|
||||||
|
<String key="LoadCopyToAll">Применить этот груз ко всем следующим подходящим транспортам</String>
|
||||||
|
<String key="WarehouseAppend">Присоединить состав со склада</String>
|
||||||
|
<String key="WarehouseImportFile">Добавить составы из файла...</String>
|
||||||
|
<String key="StartWithoutSaving">Запустить без сохранения настроек</String>
|
||||||
|
<String key="StartFreeFly">Запустить без транспорта</String>
|
||||||
|
<String key="VolumePaused">Громкость на паузе:</String>
|
||||||
|
<String key="DebugLogVisible">Показывать ход симуляции</String>
|
||||||
|
<String key="PyScreenPriority">Частота обновления дополнительных экранов:</String>
|
||||||
|
<String key="PyPriorityNormal">Стандартная</String>
|
||||||
|
<String key="PyPriorityLower">Низкая</String>
|
||||||
|
<String key="PyPriorityLowest">Очень низкая</String>
|
||||||
|
<String key="PyPriorityIdle">Нет</String>
|
||||||
|
<String key="PyPriorityOff">Отключена</String>
|
||||||
|
|
||||||
|
<String key="MassBrutto">Масса брутто</String>
|
||||||
|
<String key="VisibilityGood">Хорошая</String>
|
||||||
|
<String key="VisibilityBad">Плохая</String>
|
||||||
|
<String key="SoundPhysics">Звук | Физика</String>
|
||||||
|
<String key="DriverHead">Машинист (кабина A)</String>
|
||||||
|
<String key="DriverRear">Машинист (кабина B)</String>
|
||||||
|
<String key="DriverPassenger">Пассажир</String>
|
||||||
|
<String key="DriverNobody">Никто</String>
|
||||||
|
<String key="RemoveAllTrains">Удалить все составы со сценерии</String>
|
||||||
|
<String key="RemoveAllTrainsConfirm">Удалить транспорт из всех составов на этой сценерии?</String>
|
||||||
|
<String key="TexWorks">Ревизия:</String>
|
||||||
|
<String key="RevisionFrom">Ревизия с:</String>
|
||||||
|
<String key="RevisionTo">Ревизия по:</String>
|
||||||
|
<String key="CrewLabel">Экипаж:</String>
|
||||||
|
<String key="RuleAdd">Добавить в правило</String>
|
||||||
|
<String key="RuleNew">— новое правило —</String>
|
||||||
|
<String key="RuleRemove">Удалить из правила</String>
|
||||||
|
<String key="CatAll">Все</String>
|
||||||
|
<String key="GfxColResolution">Разрешение и текстуры</String>
|
||||||
|
<String key="GfxColRendering">Рендеринг</String>
|
||||||
|
<String key="GfxColShadows">Тени</String>
|
||||||
|
<String key="GfxColReflections">Отражения и вид</String>
|
||||||
|
<String key="GfxColScripts">Скрипты Python</String>
|
||||||
|
<String key="UartHint">Имя порта / не менять / частота отправки данных</String>
|
||||||
|
<String key="UartFeature">UARTfeature:</String>
|
||||||
|
<String key="UartSaveClose">Сохранить и закрыть</String>
|
||||||
|
<String key="PickCategoryFirst">Сначала выберите категорию или класс транспорта.</String>
|
||||||
|
<String key="HideArchivalSceneries">Скрыть архивные сценарии</String>
|
||||||
|
<String key="Tools">Инструменты</String>
|
||||||
|
<String key="AdvancedSettings">Расширенные настройки</String>
|
||||||
|
<String key="AdvLogPage">Журнал событий</String>
|
||||||
|
<String key="AdvClearLog">Очистить журнал</String>
|
||||||
|
<String key="ConsistSchedule">Состав</String>
|
||||||
</Language>
|
</Language>
|
||||||
|
|||||||
Reference in New Issue
Block a user