Complete UI rebuild

This commit is contained in:
2026-06-23 18:20:09 +02:00
parent a7bb897fc6
commit b977d224e9
13 changed files with 1322 additions and 754 deletions

View File

@@ -1,17 +1,33 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
x:Class="StarterNG.App"
x:CompileBindings="True"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
RequestedThemeVariant="Dark">
<!-- Dark variant matches the original Starter's "Carbon" VCL style. -->
<Application.Styles>
<sukiUi:SukiTheme ThemeColor="Green" />
<FluentTheme />
<materialIcons:MaterialIconStyles />
<StyleInclude Source="avares://Starter/Styles/Carbon.axaml" />
</Application.Styles>
<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"/>
<!-- 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="SystemAccentColorDark1">#136B00</Color>
<Color x:Key="SystemAccentColorDark2">#0F5600</Color>
<Color x:Key="SystemAccentColorDark3">#0B4200</Color>
<Color x:Key="SystemAccentColorLight1">#2A9A0F</Color>
<Color x:Key="SystemAccentColorLight2">#41C400</Color>
<Color x:Key="SystemAccentColorLight3">#5FD42A</Color>
<SolidColorBrush x:Key="AccentFillColorDefaultBrush" Color="#177f00" />
<SolidColorBrush x:Key="AccentFillColorSecondaryBrush" Color="#1E9000" />
<SolidColorBrush x:Key="AccentFillColorTertiaryBrush" Color="#136B00" />
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="#177f00" />
<SolidColorBrush x:Key="SystemControlHighlightListAccentMediumBrush" Color="#1E9000" />
</Application.Resources>
</Application>

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -18,6 +19,21 @@ public class Scenery
public string Description; // //$d - scenery description
public string ImageName; // //$i - main-window image (scenery thumbnail)
// Weather / environment. Like the original Starter, these are editable and are
// written into the scenery's "config" block on launch (see RewriteWeather).
// Defaults mirror the original (15 °C, day 0, 10:30, clear sky).
public string WeatherTime = "10:30"; // h:mm -> "time"/"scenario.time.override"
public int Day = 0; // "movelight <day>" (day of year / season)
public double Temperature = 15; // "scenario.weather.temperature"
public int FogEnd = 2000; // visibility in metres (atmo fog range)
public double Overcast = 0; // atmo overcast factor (-1.5 .. 1.5)
/// <summary>True when the scenery actually declared any weather command.</summary>
public bool HasWeather;
/// <summary>Set once the user edits the weather, so export rewrites the config.</summary>
public bool WeatherDirty;
// The file content with each trainset block replaced by a {{i}} placeholder,
// used to rebuild the .scn on export.
private readonly string _template;
@@ -40,9 +56,14 @@ public class Scenery
// matching //$it, etc.
this.Group = MatchDirective(content, "l");
this.Name = MatchDirective(content, "n");
this.Description = MatchDirective(content, "d");
// //$d may appear on several lines; each is one line of the description.
this.Description = MatchAllDirectives(content, "d");
this.ImageName = MatchDirective(content, "i");
// weather/environment is read from the raw text before trainset blocks
// are stripped out below (the atmosphere commands live outside trainsets)
ParseWeather(content);
// parsing trainsets
List<string> trainsetEntries = new List<string>();
@@ -71,11 +92,125 @@ public class Scenery
public string BuildExportContent()
{
string result = _template;
// Inject the (edited) weather into the config block before substituting
// trainsets, so the {{i}} placeholders shield the trainset text from the
// weather rewrite. Only done when the user actually changed the weather.
if (WeatherDirty)
result = RewriteWeather(result);
for (int i = 0; i < Trainsets.Count; i++)
result = result.Replace("{{" + i + "}}", Trainsets[i].ToSceneryEntry());
return result;
}
/// <summary>
/// Reads the scenery's environment commands into the editable weather fields.
/// Mirrors the original Starter (config: movelight / scenario.weather.temperature
/// / scenario.time.override, plus top-level time and the atmo fog/overcast block).
/// </summary>
private void ParseWeather(string content)
{
// start time: "scenario.time.override h:mm" wins, else top-level "time h:mm"
var ovr = Regex.Match(content, @"(?i)scenario\.time\.override\s+(\d{1,2})[:.](\d{2})");
var time = ovr.Success
? ovr
: Regex.Match(content, @"(?im)^\s*time\s+(\d{1,2})[:.](\d{2})\b");
if (time.Success)
{
WeatherTime = $"{time.Groups[1].Value.PadLeft(2, '0')}:{time.Groups[2].Value}";
HasWeather = true;
}
// "movelight <day>" - day of the year (sun elevation / season)
var move = Regex.Match(content, @"(?im)\bmovelight\s+(-?\d+)");
if (move.Success && int.TryParse(move.Groups[1].Value, out int day))
{
Day = day;
HasWeather = true;
}
// "scenario.weather.temperature <°C>"
var temp = Regex.Match(content, @"(?i)scenario\.weather\.temperature\s+(-?\d+(?:\.\d+)?)");
if (temp.Success &&
double.TryParse(temp.Groups[1].Value, System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out double t))
{
Temperature = t;
HasWeather = true;
}
// "atmo R G B fogStart fogEnd R G B overcast endatmo"
var atmo = Regex.Match(content, @"(?is)\batmo\b(.*?)\bendatmo\b");
if (atmo.Success)
{
var nums = Regex.Matches(atmo.Groups[1].Value, @"-?\d+(?:\.\d+)?")
.Select(m => m.Value).ToList();
if (nums.Count >= 5 && int.TryParse(nums[4], out int fog))
{
FogEnd = fog;
HasWeather = true;
}
if (nums.Count >= 6 &&
double.TryParse(nums[^1], System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out double oc))
Overcast = oc;
}
}
/// <summary>
/// Replaces the scenery's environment commands with a fresh config block built
/// from the current weather fields (the C# equivalent of the original Starter's
/// TLexParser.ChangeConfig). On any failure the original text is returned
/// unchanged so a launch is never blocked by a bad rewrite.
/// </summary>
private string RewriteWeather(string text)
{
try
{
// strip the existing weather commands
string s = text;
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)\batmo\b.*?\bendatmo\b", " ");
s = Regex.Replace(s, @"(?im)^[ \t]*movelight\s+\S+", " ");
s = Regex.Replace(s, @"(?i)scenario\.weather\.temperature\s+\S+", " ");
s = Regex.Replace(s, @"(?i)scenario\.time\.override\s+\S+", " ");
var inv = System.Globalization.CultureInfo.InvariantCulture;
string config =
"config\r\n" +
$"movelight {Day}\r\n" +
$"scenario.weather.temperature {Temperature.ToString(inv)}\r\n" +
$"scenario.time.override {WeatherTime}\r\n" +
$"time {WeatherTime} 0 0 endtime\r\n" +
$"atmo 0 0 0 {FogEnd} {FogEnd} 0 0 0 {Overcast.ToString(inv)} endatmo\r\n" +
"endconfig\r\n";
return config + s;
}
catch
{
return text;
}
}
/// <summary>
/// Collects every //$&lt;letter&gt; line (e.g. all //$d lines) and joins them
/// into one multi-line string. Returns null when none are present.
/// </summary>
private static string MatchAllDirectives(string content, string letter)
{
var matches = Regex.Matches(
content,
@"^//\$" + letter + @"\b[ \t]*([^\r\n]*)",
RegexOptions.Multiline
);
if (matches.Count == 0)
return null;
return string.Join("\n", matches.Select(m => m.Groups[1].Value.TrimEnd()));
}
/// <summary>
/// Reads a single starter directive value (the text after //$&lt;letter&gt;).
/// Returns null when the directive is absent. The trailing whitespace

View File

@@ -1,112 +1,116 @@
<sukiUi:SukiWindow
<Window
x:Class="StarterNG.MainWindow"
x:CompileBindings="True"
xmlns="https://github.com/avaloniaui"
xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:StarterNG.Views"
xmlns:starterNg="clr-namespace:StarterNG"
BackgroundStyle="Flat"
Title="MaSzyna - Symulator pojazdów szynowych" Height="800" Width="1200" WindowStartupLocation="CenterScreen">
<sukiUi:SukiWindow.RightWindowTitleBarControls>
Title="Starter MaSzyna"
Width="1200" Height="768" MinWidth="1200" MinHeight="666"
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
OS minimise / maximise / close buttons stay in the top-right corner. -->
<DockPanel LastChildFill="True">
<!-- ── Top bar: thin caption strip (row 0) + toolbar (row 1) ────────────
The OS minimise / maximise / close buttons render at the top-right of
the caption strip, so they sit ABOVE the social-icon cluster, which
lives on the right of the toolbar row. -->
<Border DockPanel.Dock="Top"
Background="{StaticResource CarbonPanelBg}"
BorderBrush="{StaticResource CarbonBorderLight}"
BorderThickness="0,0,0,1">
<Grid RowDefinitions="32,48">
<!-- Row 0: title / caption strip (draggable; OS buttons top-right) -->
<TextBlock Grid.Row="0" Text="Starter MaSzyna" VerticalAlignment="Center"
Margin="14,0,0,0" FontSize="11"
Foreground="{StaticResource CarbonFgDim}" />
<!-- Row 1: logo + navigation (left) and the right cluster (right) -->
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,*,Auto">
<!-- Logo -->
<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}"
Tag="scenarios" IsChecked="True"
IsCheckedChanged="Nav_OnCheckedChanged"
Content="{Binding [NavScenarios], Source={x:Static starterNg:App.Loc}}" />
<RadioButton x:Name="NavDepot" GroupName="nav" Theme="{StaticResource NavTab}"
Tag="depot"
IsCheckedChanged="Nav_OnCheckedChanged"
Content="{Binding [NavDepot], Source={x:Static starterNg:App.Loc}}" />
<RadioButton x:Name="NavSettings" GroupName="nav" Theme="{StaticResource NavTab}"
Tag="settings"
IsCheckedChanged="Nav_OnCheckedChanged"
Content="{Binding [NavSettings], Source={x:Static starterNg:App.Loc}}" />
<Button Classes="Basic" Width="48" Cursor="Hand"
ToolTip.Tip="{Binding [Help], Source={x:Static starterNg:App.Loc}}"
Tag="https://wiki.eu07.pl/" Click="linkClick"
Content="?" FontWeight="Bold" FontSize="16"
HorizontalContentAlignment="Center" />
</StackPanel>
<!-- Right cluster: version, language, log, social links.
In the toolbar row, so it sits below the OS caption buttons. -->
<StackPanel Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center"
Margin="0,0,8,0" Spacing="4">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="0,0,8,0">
<Label Content="{Binding [Version], Source={x:Static starterNg:App.Loc}}"
FontSize="10" Foreground="{StaticResource CarbonFgDim}" Padding="0" />
<Label Content="{x:Static starterNg:AppInfo.Version}"
FontSize="10" Foreground="{StaticResource CarbonFg}" Padding="0" />
</StackPanel>
<ComboBox x:Name="LangCombo" Width="100" VerticalAlignment="Center"
SelectionChanged="LangCombo_OnSelectionChanged">
<ComboBoxItem>Polski</ComboBoxItem>
<ComboBoxItem>English</ComboBoxItem>
</ComboBox>
<Button Classes="Basic" Click="openLastLogClick"
ToolTip.Tip="{Binding [ShowLatestLogTooltip], Source={x:Static starterNg:App.Loc}}">
<materialIcons:MaterialIcon Kind="FileDocument"/>
<materialIcons:MaterialIcon Kind="FileDocument" />
</Button>
<Label VerticalAlignment="Center" Content="{x:Static starterNg:AppInfo.Version}"/>
</sukiUi:SukiWindow.RightWindowTitleBarControls>
<sukiUi:SukiSideMenu IsMenuExpanded="False">
<sukiUi:SukiSideMenu.Items>
<!-- SCENARIUSZE -->
<sukiUi:SukiSideMenuItem
Header="{Binding [NavScenarios], Source={x:Static starterNg:App.Loc}}"
Classes="Compact">
<sukiUi:SukiSideMenuItem.Icon>
<materialIcons:MaterialIcon Kind="ListBox" />
</sukiUi:SukiSideMenuItem.Icon>
<sukiUi:SukiSideMenuItem.PageContent>
<views:Scenarios />
</sukiUi:SukiSideMenuItem.PageContent>
</sukiUi:SukiSideMenuItem>
<!-- Lokomotywownia -->
<sukiUi:SukiSideMenuItem
Header="{Binding [NavDepot], Source={x:Static starterNg:App.Loc}}"
Classes="Compact">
<sukiUi:SukiSideMenuItem.Icon>
<materialIcons:MaterialIcon Kind="Train" />
</sukiUi:SukiSideMenuItem.Icon>
<sukiUi:SukiSideMenuItem.PageContent>
<views:Depot />
</sukiUi:SukiSideMenuItem.PageContent>
</sukiUi:SukiSideMenuItem>
<!-- Ustawienia -->
<sukiUi:SukiSideMenuItem
Header="{Binding [NavSettings], Source={x:Static starterNg:App.Loc}}"
Classes="Compact">
<sukiUi:SukiSideMenuItem.Icon>
<materialIcons:MaterialIcon Kind="Settings" />
</sukiUi:SukiSideMenuItem.Icon>
<sukiUi:SukiSideMenuItem.PageContent>
<views:Settings/>
</sukiUi:SukiSideMenuItem.PageContent>
</sukiUi:SukiSideMenuItem>
<!-- Sterowanie -->
<sukiUi:SukiSideMenuItem
Header="{Binding [NavControls], Source={x:Static starterNg:App.Loc}}"
Classes="Compact">
<sukiUi:SukiSideMenuItem.Icon>
<materialIcons:MaterialIcon Kind="Controller" />
</sukiUi:SukiSideMenuItem.Icon>
<sukiUi:SukiSideMenuItem.PageContent>
<Label>Work in progress</Label>
</sukiUi:SukiSideMenuItem.PageContent>
</sukiUi:SukiSideMenuItem>
</sukiUi:SukiSideMenu.Items>
<sukiUi:SukiSideMenu.HeaderContent>
<!-- Logo PNG. Replace Assets/Logo/maszyna_white.png (white logo for the
dark side-menu header). A maszyna_black.png variant also lives in
Assets/Logo for light backgrounds - swap the Source if needed. -->
<Image
Source="/Assets/Logo/maszyna_white.png"
Width="200"
Margin="16"
Height="60" />
</sukiUi:SukiSideMenu.HeaderContent>
<sukiUi:SukiSideMenu.FooterContent>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<!-- eu07.pl -->
<Button Classes="Basic" Cursor="Hand" Tag="http://eu07.pl/" Click="linkClick">
<materialIcons:MaterialIcon Kind="Web" />
</Button>
<!-- Discord -->
<Button Classes="Basic" Cursor="Hand" Tag="https://discord.gg/eu07" Click="linkClick">
<PathIcon Data="{StaticResource DiscordLogo}" Height="12" />
</Button>
<!-- Facebook -->
<Button Classes="Basic" Cursor="Hand" Tag="http://www.facebook.com/MaSzynaeu07pl" Click="linkClick">
<materialIcons:MaterialIcon Kind="Facebook" />
</Button>
<!-- Youtube -->
<Button Classes="Basic" Cursor="Hand" Tag="http://www.youtube.com/@eu07official" Click="linkClick">
<materialIcons:MaterialIcon Kind="Youtube" />
</Button>
<!-- Github -->
<Button Classes="Basic" Cursor="Hand" Tag="http://github.com/MaSzyna-EU07/" Click="linkClick">
<materialIcons:MaterialIcon Kind="Github" />
</Button>
</StackPanel>
</sukiUi:SukiSideMenu.FooterContent>
</sukiUi:SukiSideMenu>
</sukiUi:SukiWindow>
</Grid>
</Grid>
</Border>
<!-- ── Full-width START button (bottom) ─────────────────────────────── -->
<Button DockPanel.Dock="Bottom" Classes="Start" Height="44" Margin="6"
Click="StartButton_OnClick"
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}" />
<!-- ── Page host (one view visible at a time, like the original PageControl) -->
<Panel Margin="6,6,6,0">
<views:Scenarios x:Name="ScenariosView" />
<views:Depot x:Name="DepotView" IsVisible="False" />
<views:Settings x:Name="SettingsView" IsVisible="False" />
</Panel>
</DockPanel>
</Window>

View File

@@ -1,21 +1,59 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Avalonia.Controls;
using Avalonia.Interactivity;
using SukiUI.Controls;
using StarterNG.Classes;
namespace StarterNG;
public partial class MainWindow : SukiWindow
public partial class MainWindow : Window
{
public MainWindow()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
InitializeComponent();
// Extend the client area over the title bar so the logo + navigation sit
// on the top bar, while the OS caption buttons (min / max / close) stay in
// the top-right corner. The default chrome hints already keep the system
// caption buttons, so only the two supported hints are set here (this
// Avalonia build no longer exposes ExtendClientAreaChromeHints).
ExtendClientAreaToDecorationsHint = true;
// Match the thin caption strip (row 0 of the top bar): the OS min/max/close
// buttons fill this height at the top-right, sitting above the social row.
ExtendClientAreaTitleBarHeightHint = 32;
// Mirror the active language into the top-bar selector.
LangCombo.SelectedIndex = App.Loc.CurrentLanguage == "Polski" ? 0 : 1;
}
// ── Navigation: show the page whose RadioButton was just checked ──────────
private void Nav_OnCheckedChanged(object? sender, RoutedEventArgs e)
{
if (sender is not RadioButton { IsChecked: true } rb)
return;
// Guard against the controls not being created yet during initial load.
if (ScenariosView is null || DepotView is null || SettingsView is null)
return;
string page = rb.Tag as string ?? "scenarios";
ScenariosView.IsVisible = page == "scenarios";
DepotView.IsVisible = page == "depot";
SettingsView.IsVisible = page == "settings";
}
private void LangCombo_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (sender is not ComboBox { SelectedItem: ComboBoxItem item }) return;
if (!item.IsKeyboardFocusWithin) return; // ignore the programmatic initial set
var lang = item.Content?.ToString();
if (!string.IsNullOrEmpty(lang))
App.ApplyLanguage(lang);
}
private void linkClick(object? sender, RoutedEventArgs e)
{
@@ -42,4 +80,53 @@ public partial class MainWindow : SukiWindow
});
}
}
// ── START: export the (possibly depot-modified) scenery and launch the sim.
// Global on the main window, like the original Starter's bottom START button.
private void StartButton_OnClick(object? sender, RoutedEventArgs e)
{
var scenery = AppState.Instance.CurrentScenery;
if (scenery is null)
return;
var trainset = AppState.Instance.CurrentTrainset;
// write scenery/$<name>.scn with the replaced trainsets
string dir = Path.GetDirectoryName(scenery.Path) ?? "scenery";
string exportName = "$" + Path.GetFileName(scenery.Path);
string exportPath = Path.Combine(dir, exportName);
try
{
File.WriteAllText(exportPath, scenery.BuildExportContent(), Encoding.GetEncoding(1250));
}
catch
{
return; // couldn't write the scenery file
}
// the player's vehicle is the node name of the driven car in the consist
string? vehicle = trainset?.Vehicles
.FirstOrDefault(v => v.DriverType is eDriverType.Headdriver or eDriverType.Reardriver)?.Name
?? trainset?.Vehicles.FirstOrDefault()?.Name;
// make sure the latest settings are on disk before the sim reads them
Settings.Instance.CaptureAndSave();
// launch the game: -s $<name>.scn -v <vehicle>
try
{
Process.Start(new ProcessStartInfo(Settings.Instance.ExecutablePath)
{
Arguments = $"-s {exportName} -v {vehicle}",
UseShellExecute = true
});
}
catch
{
return; // executable not found / not configured yet
}
// honour the "close starter automatically" preference
if (Settings.Instance.AutoCloseStarter)
Close();
}
}

View File

@@ -8,8 +8,8 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<AssemblyVersion>1.0.0.1 </AssemblyVersion>
<FileVersion>1.0.0.1</FileVersion>
<AssemblyName>Starter</AssemblyName>
<Company>eu07.pl</Company>
</PropertyGroup>
@@ -32,7 +32,6 @@
</PackageReference>
<PackageReference Include="HotAvalonia" Version="3.0.2" />
<PackageReference Include="Material.Icons.Avalonia" Version="3.0.2" />
<PackageReference Include="SukiUI" Version="7.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,190 @@
<Styles xmlns="https://github.com/avaloniaui"
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>
<!-- ── Palette ──────────────────────────────────────────────────── -->
<Color x:Key="CarbonAppBgColor">#15171B</Color>
<Color x:Key="CarbonPanelBgColor">#1E2327</Color>
<Color x:Key="CarbonHeaderBgColor">#2A3036</Color>
<Color x:Key="CarbonInputBgColor">#262C31</Color>
<Color x:Key="CarbonAccentColor">#177f00</Color>
<SolidColorBrush x:Key="CarbonAppBg" Color="#15171B" />
<SolidColorBrush x:Key="CarbonPanelBg" Color="#1E2327" />
<SolidColorBrush x:Key="CarbonHeaderBg" Color="#2A3036" />
<SolidColorBrush x:Key="CarbonHoverBg" Color="#2A3036" />
<SolidColorBrush x:Key="CarbonInputBg" Color="#262C31" />
<SolidColorBrush x:Key="CarbonBorderLight" Color="#3A424A" />
<SolidColorBrush x:Key="CarbonBorderDark" Color="#0E0F12" />
<SolidColorBrush x:Key="CarbonFg" Color="#E6E8EA" />
<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="CarbonAccentHover" 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">
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{StaticResource CarbonPanelBg}"
BorderBrush="{StaticResource CarbonBorderLight}"
BorderThickness="1" CornerRadius="2">
<DockPanel LastChildFill="True">
<Border DockPanel.Dock="Top"
Background="{StaticResource CarbonHeaderBg}"
BorderBrush="{StaticResource CarbonBorderLight}"
BorderThickness="0,0,0,1"
Padding="8,5">
<TextBlock Text="{TemplateBinding Header}"
FontWeight="SemiBold"
Foreground="{StaticResource CarbonFg}" />
</Border>
<ContentPresenter Content="{TemplateBinding Content}"
Margin="8" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
<!-- ── Top navigation tab (RadioButton) ─────────────────────────────
Use via Theme="{StaticResource NavTab}". Single GroupName = one active. -->
<ControlTheme x:Key="NavTab" TargetType="RadioButton">
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="FontSize" Value="14" />
<Setter Property="MinWidth" Value="150" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="root" Background="Transparent"
BorderBrush="Transparent" BorderThickness="0,0,0,2">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ Border#root">
<Setter Property="Background" Value="{StaticResource CarbonHoverBg}" />
</Style>
<Style Selector="^:checked /template/ Border#root">
<Setter Property="Background" Value="{StaticResource CarbonHoverBg}" />
<Setter Property="BorderBrush" Value="{StaticResource CarbonAccentBright}" />
</Style>
<Style Selector="^:checked">
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
</Style>
</ControlTheme>
</Styles.Resources>
<!-- ── Window / page surfaces ─────────────────────────────────────────── -->
<Style Selector="Window">
<Setter Property="Background" Value="{StaticResource CarbonAppBg}" />
<Setter Property="Foreground" Value="{StaticResource CarbonFg}" />
<Setter Property="FontFamily" Value="Tahoma, Segoe UI, Inter" />
</Style>
<!-- ── START button (full-width, bold) ────────────────────────────────── -->
<Style Selector="Button.Start">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="16" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="2" />
</Style>
<Style Selector="Button.Start /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
</Style>
<Style Selector="Button.Start:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccentHover}" />
<Setter Property="TextBlock.Foreground" Value="White" />
</Style>
<Style Selector="Button.Start:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
</Style>
<!-- ── Accent button (Save etc.) ──────────────────────────────────────── -->
<Style Selector="Button.Accent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
<Setter Property="TextBlock.Foreground" Value="White" />
</Style>
<Style Selector="Button.Accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccentHover}" />
<Setter Property="TextBlock.Foreground" Value="White" />
</Style>
<!-- ── Flat button (toolbar/secondary) ────────────────────────────────── -->
<Style Selector="Button.Flat">
<Setter Property="Background" Value="{StaticResource CarbonInputBg}" />
<Setter Property="BorderBrush" Value="{StaticResource CarbonBorderLight}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="2" />
</Style>
<!-- ── Basic button (borderless icon buttons) ─────────────────────────── -->
<Style Selector="Button.Basic">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="6" />
<Setter Property="Foreground" Value="{StaticResource CarbonFgDim}" />
</Style>
<Style Selector="Button.Basic /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.Basic:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonHoverBg}" />
<Setter Property="TextBlock.Foreground" Value="{StaticResource CarbonFg}" />
</Style>
<!-- ── Selection highlight in MaSzyna green (not the OS accent) ────────── -->
<Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
</Style>
<Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccentHover}" />
</Style>
<Style Selector="ListBoxItem:selected:focus /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
</Style>
<Style Selector="TreeViewItem:selected /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
</Style>
<Style Selector="TreeViewItem:selected:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{StaticResource CarbonAccentHover}" />
</Style>
<Style Selector="ComboBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource CarbonAccent}" />
</Style>
<!-- ── Tabs: smaller headers (Fluent's default is oversized) ──────────── -->
<Style Selector="TabItem">
<Setter Property="FontSize" Value="14" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Padding" Value="10,6" />
</Style>
<!-- ── Compact list (tight rows that fill the available space) ─────────── -->
<Style Selector="ListBox.Compact">
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="ListBox.Compact ListBoxItem">
<Setter Property="Padding" Value="6,2" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="FontSize" Value="12" />
</Style>
</Styles>

View File

@@ -1,14 +1,13 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:starterNg="clr-namespace:StarterNG"
xmlns:views="clr-namespace:StarterNG.Views"
x:CompileBindings="True"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
x:Class="StarterNG.Views.Depot">
<Grid Margin="16" ColumnSpacing="16" RowSpacing="16">
<Grid Margin="0" ColumnSpacing="8" RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="340" />
<ColumnDefinition />
@@ -20,28 +19,46 @@
</Grid.RowDefinitions>
<!-- Vehicle database browser (left, rows 0-1) -->
<sukiUi:GroupBox Grid.RowSpan="2"
<HeaderedContentControl Grid.RowSpan="2" Theme="{StaticResource GroupBox}"
Header="{Binding [VehicleDatabase], Source={x:Static starterNg:App.Loc}}">
<DockPanel LastChildFill="True">
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*" ColumnSpacing="6" Margin="0,0,0,6">
<ComboBox Name="categoryCombo" Grid.Column="0" HorizontalAlignment="Stretch"
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
SelectionChanged="CategoryCombo_OnSelectionChanged" />
<ComboBox Name="classCombo" Grid.Column="1" HorizontalAlignment="Stretch"
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
SelectionChanged="ClassCombo_OnSelectionChanged" />
</Grid>
<TextBox Name="searchBox" DockPanel.Dock="Top" Margin="0,0,0,8"
Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
TextChanged="SearchBox_OnTextChanged" />
<!-- Folded groups (built in code-behind); collapsed groups hold no
controls, so the list stays light and scrolls smoothly. -->
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Name="browserStack" Spacing="2" />
</ScrollViewer>
<!-- Mini preview of the selected entry + the single Add button.
Both are docked at the bottom, under the flat vehicle list. -->
<Button Name="addVehicleButton" DockPanel.Dock="Bottom" Classes="Flat Accent"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
Margin="0,8,0,0" IsEnabled="False" Cursor="Hand"
Click="AddVehicleButton_OnClick"
Content="{Binding [AddVehicle], Source={x:Static starterNg:App.Loc}}" />
<Border Name="miniPreviewPanel" DockPanel.Dock="Bottom" Height="62" Margin="0,6,0,0"
Background="{StaticResource CarbonInputBg}"
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
CornerRadius="2">
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
</Border>
<!-- Flat list of vehicle entries (plain text, no expanders).
Selecting one shows its mini and enables Add; double-click
replaces the selected consist vehicle. -->
<ListBox Name="vehicleListBox" Classes="Compact"
SelectionChanged="VehicleListBox_OnSelectionChanged"
DoubleTapped="VehicleListBox_OnDoubleTapped" />
</DockPanel>
</sukiUi:GroupBox>
</HeaderedContentControl>
<!-- Scenery + load consist (top-right) -->
<sukiUi:GroupBox Grid.Column="1" Grid.Row="0"
<HeaderedContentControl Grid.Column="1" Grid.Row="0" Theme="{StaticResource GroupBox}"
Header="{Binding [LoadFromScenery], Source={x:Static starterNg:App.Loc}}">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="6">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
@@ -54,10 +71,10 @@
DropDownOpened="SceneryConsistCombo_OnDropDownOpened"
SelectionChanged="SceneryConsistCombo_OnSelectionChanged" />
</Grid>
</sukiUi:GroupBox>
</HeaderedContentControl>
<!-- Brakes / Loads editor (middle-right) -->
<sukiUi:GroupBox Grid.Column="1" Grid.Row="1"
<HeaderedContentControl Grid.Column="1" Grid.Row="1" Theme="{StaticResource GroupBox}"
Header="{Binding [SelectedVehicle], Source={x:Static starterNg:App.Loc}}">
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0,0,0,8">
@@ -81,10 +98,10 @@
</TabItem>
</TabControl>
</DockPanel>
</sukiUi:GroupBox>
</HeaderedContentControl>
<!-- Consist (full-width bottom, horizontally scrollable) -->
<sukiUi:GroupBox Grid.Row="2" Grid.ColumnSpan="2"
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
<Panel>
<TextBlock Name="emptyHint" VerticalAlignment="Center" HorizontalAlignment="Center"
@@ -95,6 +112,6 @@
VerticalAlignment="Center" Spacing="0" />
</ScrollViewer>
</Panel>
</sukiUi:GroupBox>
</HeaderedContentControl>
</Grid>
</UserControl>

View File

@@ -29,8 +29,7 @@ public partial class Depot : UserControl
// Shared, size-limited thumbnail cache (decoded down to display height).
private readonly Dictionary<string, Bitmap?> _miniCache = new();
private const int BrowserThumbHeight = 38;
private const int ConsistThumbHeight = 50;
private const int ConsistThumbHeight = 34;
private readonly Cursor _hand = new(StandardCursorType.Hand);
private int _nameCounter;
@@ -44,18 +43,21 @@ public partial class Depot : UserControl
// scenery-consist previews are built lazily on first dropdown open
private bool _consistPreviewsBuilt;
// auto-expand search results only when there are few of them
private const int AutoExpandLimit = 40;
private const int MaxRowsPerGroup = 250;
// active database filters
private Func<string?, bool>? _categoryFilter; // matches a category letter, null = all
private string? _classFilter; // group mini (e.g. EU07), null = all
private Func<string?, bool>? _categoryFilter; // matches a category letter, null = none chosen
private string? _classFilter; // group mini (e.g. EU07), null = all in category
// the vehicle currently highlighted in the flat list (drives the mini preview
// and the single Add button)
private VehicleTexture? _browserSelected;
// hard cap on how many entries the flat list shows at once
private const int MaxListRows = 2000;
// card highlight brushes (theme-neutral overlays)
private static readonly IBrush CardBorder = new SolidColorBrush(Color.Parse("#33888888"));
private static readonly IBrush CardBorderSel = new SolidColorBrush(Color.Parse("#AA4D8BFF"));
private static readonly IBrush CardBgSel = new SolidColorBrush(Color.Parse("#224D8BFF"));
private static readonly IBrush CardBorderSel = new SolidColorBrush(Color.Parse("#AA41C400"));
private static readonly IBrush CardBgSel = new SolidColorBrush(Color.Parse("#2241C400"));
private static readonly IBrush Placeholder = new SolidColorBrush(Color.Parse("#22808080"));
// Vehicle types, keyed by the JSON "category" letter. Powered/technical
@@ -132,7 +134,7 @@ public partial class Depot : UserControl
_searchTimer.Tick += (_, _) =>
{
_searchTimer!.Stop();
BuildBrowser();
BuildList();
};
// Defer population so the tab renders immediately, then fills in.
@@ -275,12 +277,13 @@ public partial class Depot : UserControl
private void PopulateCategoryCombo()
{
// No "All" entry: nothing is selected by default, so the list starts empty
// (matching the original Starter's depot).
_suppress = true;
categoryCombo.Items.Clear();
categoryCombo.Items.Add(new ComboBoxItem { Content = App.Loc["All"], Tag = null });
foreach (var (locKey, match) in CategoryDefs)
categoryCombo.Items.Add(new ComboBoxItem { Content = App.Loc[locKey], Tag = match });
categoryCombo.SelectedIndex = 0;
categoryCombo.SelectedIndex = -1;
_suppress = false;
_categoryFilter = null;
@@ -289,18 +292,16 @@ public partial class Depot : UserControl
private void RebuildClassCombo()
{
// No "All" entry: an unselected class means "every class in the category".
_suppress = true;
classCombo.Items.Clear();
classCombo.Items.Add(new ComboBoxItem { Content = App.Loc["All"], Tag = null });
foreach (string cls in ClassesForCategory(_categoryFilter))
classCombo.Items.Add(new ComboBoxItem { Content = cls, Tag = cls });
classCombo.SelectedIndex = 0;
classCombo.SelectedIndex = -1;
_suppress = false;
_classFilter = null;
BuildBrowser();
BuildList();
}
private IEnumerable<string> ClassesForCategory(Func<string?, bool>? category) =>
@@ -322,87 +323,83 @@ public partial class Depot : UserControl
{
if (_suppress) return;
_classFilter = (classCombo.SelectedItem as ComboBoxItem)?.Tag as string;
BuildBrowser();
BuildList();
}
// ----------------------------------------------------------- vehicle browser
// Browser groups by the JSON "group" field, rendered as collapsed expanders.
// Collapsed groups hold no controls, so the list stays light and smooth.
private void BuildBrowser()
// Flat list of plain-text vehicle entries (no expanders, like the original
// Starter). Empty by default: an entry only appears once a category is chosen
// or a search is typed. Picking one drives the mini preview and Add button.
private void BuildList()
{
browserStack.Children.Clear();
// reset selection-dependent UI
_browserSelected = null;
miniPreview.Source = null;
addVehicleButton.IsEnabled = false;
vehicleListBox.Items.Clear();
string search = searchBox.Text?.Trim() ?? "";
bool hasSearch = search.Length > 0;
var matched = _db.Textures.Where(t => PassesFilters(t, search, hasSearch)).ToList();
// nothing selected and nothing searched -> keep the list empty
if (_categoryFilter == null && !hasSearch)
return;
// Only auto-open groups when the search is specific. A broad search keeps
// groups folded (just headers + counts) so we never realize thousands of
// rows / decode thousands of bitmaps at once.
bool autoExpand = hasSearch && matched.Count <= AutoExpandLimit;
// fold by class (the group's mini)
var grouped = matched
.GroupBy(ClassOf)
.OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase);
foreach (var group in grouped)
{
var list = group
var matched = _db.Textures
.Where(t => PassesFilters(t, search, hasSearch))
.OrderBy(t => t.TextureMini ?? t.Skinfile, StringComparer.OrdinalIgnoreCase)
.Take(MaxListRows)
.ToList();
string header = string.IsNullOrEmpty(group.Key)
? App.Loc["Others"]
: group.Key;
var content = new StackPanel { Spacing = 2 };
var expander = new Expander
foreach (var texture in matched)
{
Header = $"{header} ({list.Count})",
IsExpanded = autoExpand, // folded by default
HorizontalAlignment = HorizontalAlignment.Stretch
};
expander.Content = content;
bool populated = false;
void Populate()
vehicleListBox.Items.Add(new ListBoxItem
{
if (populated) return;
populated = true;
int shown = Math.Min(list.Count, MaxRowsPerGroup);
for (int i = 0; i < shown; i++)
content.Children.Add(BuildBrowserItem(list[i]));
if (list.Count > shown)
content.Children.Add(new TextBlock
{
Text = $"… +{list.Count - shown}",
Opacity = 0.6,
Margin = new Thickness(6, 2)
Content = BrowserLabel(texture, _db.ResolveSet(texture)),
Tag = texture
});
}
if (expander.IsExpanded)
Populate();
expander.Expanded += (_, _) => Populate();
browserStack.Children.Add(expander);
}
if (browserStack.Children.Count == 0)
if (vehicleListBox.Items.Count == 0)
vehicleListBox.Items.Add(new ListBoxItem
{
browserStack.Children.Add(new TextBlock
{
Text = App.Loc["NoVehicles"],
Opacity = 0.6,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(4)
Content = App.Loc["NoVehicles"],
IsEnabled = false
});
}
// Selecting an entry shows its mini preview and enables the Add button.
private void VehicleListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (vehicleListBox.SelectedItem is ListBoxItem { Tag: VehicleTexture texture })
{
_browserSelected = texture;
miniPreview.Source = GetMiniBitmap(_db.ResolveMiniName(texture), 54);
addVehicleButton.IsEnabled = true;
}
else
{
_browserSelected = null;
miniPreview.Source = null;
addVehicleButton.IsEnabled = false;
}
}
// Double-click an entry to replace the selected consist vehicle.
private void VehicleListBox_OnDoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
if (_browserSelected != null)
ReplaceSelected(_browserSelected);
}
// The single Add button inserts the highlighted vehicle after the selected
// consist vehicle (or at the end when nothing is selected).
private void AddVehicleButton_OnClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (_browserSelected != null)
AddTexture(_browserSelected);
}
private bool PassesFilters(VehicleTexture t, string search, bool hasSearch)
@@ -428,54 +425,6 @@ public partial class Depot : UserControl
|| C(t.Meta?.Vehicle) || C(t.Meta?.Operator) || C(ClassOf(t));
}
private Control BuildBrowserItem(VehicleTexture texture)
{
// Grid with a star label column keeps the label width constrained so it
// wraps instead of overlapping the Add button.
var grid = new Grid
{
Margin = new Thickness(2, 1),
ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto")
};
ToolTip.SetTip(grid, texture.FullPath);
var thumb = CreateLazyMini(_db.ResolveMiniName(texture), BrowserThumbHeight, 64);
thumb.VerticalAlignment = VerticalAlignment.Center;
Grid.SetColumn(thumb, 0);
var set = _db.ResolveSet(texture);
var label = new TextBlock
{
Text = BrowserLabel(texture, set),
VerticalAlignment = VerticalAlignment.Center,
FontSize = 12,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(8, 0, 8, 0)
};
Grid.SetColumn(label, 1);
var add = new Button
{
Content = App.Loc["AddVehicle"],
FontSize = 11,
Padding = new Thickness(10, 3),
VerticalAlignment = VerticalAlignment.Center,
Cursor = _hand
};
add.Classes.Add("Flat");
ToolTip.SetTip(add, set != null ? App.Loc["TipAddUnit"] : App.Loc["TipAddVehicle"]);
add.Click += (_, _) => AddTexture(texture);
Grid.SetColumn(add, 2);
grid.Children.Add(thumb);
grid.Children.Add(label);
grid.Children.Add(add);
// double-click the row to replace the selected consist vehicle
grid.DoubleTapped += (_, _) => ReplaceSelected(texture);
return grid;
}
// Replaces the selected consist unit with this vehicle/unit (keeping its
// slot, crew and orientation). Falls back to appending if nothing is selected.
private void ReplaceSelected(VehicleTexture texture)
@@ -506,29 +455,6 @@ public partial class Depot : UserControl
RebuildConsist();
}
// An image that decodes its mini only while it is on screen, and releases it
// when scrolled out of view, so an open group never holds every bitmap at once.
private Image CreateLazyMini(string? miniName, int height, double width)
{
var img = new Image { Height = height, Width = width, Stretch = Stretch.Uniform };
bool loaded = false;
img.EffectiveViewportChanged += (_, e) =>
{
bool visible = e.EffectiveViewport.Width > 0 && e.EffectiveViewport.Height > 0;
if (visible && !loaded)
{
img.Source = GetMiniBitmap(miniName, height);
loaded = true;
}
else if (!visible && loaded)
{
img.Source = null;
loaded = false;
}
};
return img;
}
private string BrowserLabel(VehicleTexture texture, IReadOnlyList<VehicleTexture>? set)
{
string name = !string.IsNullOrEmpty(texture.TextureMini)
@@ -644,13 +570,13 @@ public partial class Depot : UserControl
foreach (var v in trainset.Vehicles)
{
var bmp = GetMiniBitmap(_db.MiniForSkin(v.SkinFile) ?? v.MiniName, 28);
var bmp = GetMiniBitmap(_db.MiniForSkin(v.SkinFile) ?? v.MiniName, 20);
if (bmp != null)
minis.Children.Add(new Image { Source = bmp, Height = 28, Stretch = Stretch.Uniform });
minis.Children.Add(new Image { Source = bmp, Height = 20, Stretch = Stretch.Uniform });
else
minis.Children.Add(new Border
{
Height = 28, Width = 46, Background = Placeholder, CornerRadius = new CornerRadius(3)
Height = 20, Width = 32, Background = Placeholder, CornerRadius = new CornerRadius(3)
});
names.Children.Add(new TextBlock { Text = v.SkinFile, FontSize = 10, Opacity = 0.8 });

View File

@@ -1,68 +1,161 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:starterNg="clr-namespace:StarterNG"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="StarterNG.Views.Scenarios"
x:CompileBindings="True">
<Grid Margin="16" ColumnSpacing="16" RowSpacing="16">
<Grid Margin="0" ColumnSpacing="8" RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="280" />
<ColumnDefinition />
<ColumnDefinition Width="280" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="100" />
<RowDefinition Height="40" />
<RowDefinition Height="78" />
</Grid.RowDefinitions>
<sukiUi:GroupBox Grid.RowSpan="2" Header="{Binding [Sceneries], Source={x:Static starterNg:App.Loc}}">
<TreeView Name="sceneryList" SelectionChanged="SceneryList_OnSelectionChanged">
</TreeView>
</sukiUi:GroupBox>
<sukiUi:GroupBox Grid.Column="1" Header="{Binding [Vehicles], Source={x:Static starterNg:App.Loc}}">
<ListBox Name="vehicleList" SelectionChanged="VehicleList_OnSelectionChanged">
</ListBox>
</sukiUi:GroupBox>
<sukiUi:GroupBox Grid.Column="1" Grid.Row="1" Header="{Binding [ScenarioDescription], Source={x:Static starterNg:App.Loc}}">
<!-- Scenery tree (left) -->
<HeaderedContentControl Grid.Column="0" Grid.Row="0" Theme="{StaticResource GroupBox}"
Classes="Compact"
Header="{Binding [Sceneries], Source={x:Static starterNg:App.Loc}}">
<TreeView Name="sceneryList" SelectionChanged="SceneryList_OnSelectionChanged" />
</HeaderedContentControl>
<!-- Centre: trainsets list (compact, fills) + description/weather/timetable tabs -->
<Grid Grid.Column="1" Grid.Row="0" RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="3*" />
</Grid.RowDefinitions>
<HeaderedContentControl Grid.Row="0" Theme="{StaticResource GroupBox}"
Header="{Binding [Vehicles], Source={x:Static starterNg:App.Loc}}">
<ListBox Name="vehicleList" Classes="Compact"
SelectionChanged="VehicleList_OnSelectionChanged" />
</HeaderedContentControl>
<TabControl Grid.Row="1">
<TabItem Header="{Binding [TabDescription], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock TextWrapping="Wrap" Name="missionDescription">
</TextBlock>
<TextBlock TextWrapping="Wrap" Name="missionDescription" Margin="4" />
</ScrollViewer>
</sukiUi:GroupBox>
<!-- Scenery image (//$i) shown as a 1:1 square thumbnail -->
<Border Grid.Column="2" Grid.Row="0" ClipToBounds="True" VerticalAlignment="Top"
Height="{Binding $self.Bounds.Width}">
<Image Name="sceneryImage" Stretch="Uniform" />
</Border>
<!-- Scenery description (//$d), distinct from the scenario/consist description -->
<sukiUi:GroupBox Grid.Column="2" Grid.Row="1" Header="{Binding [SceneryDescription], Source={x:Static starterNg:App.Loc}}">
</TabItem>
<TabItem Header="{Binding [Weather], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock TextWrapping="Wrap" Name="sceneryDescription">
</TextBlock>
</ScrollViewer>
</sukiUi:GroupBox>
<StackPanel Name="weatherForm" Margin="8" Spacing="8" IsEnabled="False">
<Grid ColumnDefinitions="130,*" RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<sukiUi:GroupBox Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}" Grid.Row="2" Grid.ColumnSpan="3">
<ScrollViewer VerticalAlignment="Bottom"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled">
<StackPanel Orientation="Horizontal" Name="consistStack">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
Text="{Binding [Time], Source={x:Static starterNg:App.Loc}}" />
<TextBox Grid.Row="0" Grid.Column="1" Name="weatherTime" Width="90"
HorizontalAlignment="Left" Watermark="hh:mm"
LostFocus="Weather_OnChanged" />
<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">
<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="204" Content="{Binding [SeasonSummer], 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}}" />
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center"
Text="{Binding [Day], Source={x:Static starterNg:App.Loc}}" />
<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="-20" Maximum="40" 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="50" Maximum="2500" Width="220"
TickFrequency="50" 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>
<TextBlock Name="weatherHint" Opacity="0.6" FontSize="11" TextWrapping="Wrap"
Text="{Binding [WeatherHint], Source={x:Static starterNg:App.Loc}}" />
</StackPanel>
</ScrollViewer>
</sukiUi:GroupBox>
</TabItem>
<TabItem Header="{Binding [Timetable], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<TextBlock Name="timetableContent" Margin="4" FontFamily="Consolas, monospace"
FontSize="12" />
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
<Button Grid.Column="0" Height="36" Classes="Flat"
VerticalAlignment="Bottom" Grid.Row="3" Grid.ColumnSpan="3"
Click="StartButton_OnClick"
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}">
<!-- Right: scenery image (//$i) + scenery description (//$d) -->
<Grid Grid.Column="2" Grid.Row="0" RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
</Button>
<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}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock TextWrapping="Wrap" Name="sceneryDescription" />
</ScrollViewer>
</HeaderedContentControl>
</Grid>
<!-- Consist preview (full-width bottom, horizontally scrollable) -->
<HeaderedContentControl Grid.Row="1" Grid.ColumnSpan="3" Theme="{StaticResource GroupBox}"
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Name="consistStack" />
</ScrollViewer>
</HeaderedContentControl>
</Grid>
</UserControl>

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
@@ -16,6 +17,11 @@ public partial class Scenarios : UserControl
{
public List<Scenery> Sceneries;
// scenery whose weather the form is currently editing
private Scenery? _currentScenery;
// suppresses weather change handlers while the form is being populated
private bool _loadingWeather;
public Scenarios()
{
InitializeComponent();
@@ -76,6 +82,11 @@ public partial class Scenarios : UserControl
// scenery-level info (distinct from the per-consist scenario description)
ShowSceneryInfo(selectedScn);
ShowWeather(selectedScn);
// no consist chosen yet for this scenery
missionDescription.Text = "";
timetableContent.Text = App.Loc["NoTimetable"];
// add all trainsets to list
for (int i = 0; i < selectedScn.Trainsets.Count; i++)
@@ -136,6 +147,135 @@ public partial class Scenarios : UserControl
AppState.Instance.CurrentTrainset = selectedTrainset;
ShowConsist(selectedTrainset);
ShowTimetable(selectedScn, selectedTrainset);
}
// Loads the scenery's weather into the editable form (Weather tab). Editing a
// control writes the value back onto the scenery and marks it dirty, so the
// change is injected into the exported .scn's config block on launch.
private void ShowWeather(Scenery scenery)
{
_currentScenery = scenery;
_loadingWeather = true;
try
{
weatherForm.IsEnabled = true;
weatherTime.Text = scenery.WeatherTime;
weatherDay.Value = scenery.Day;
weatherTemp.Value = scenery.Temperature;
weatherFog.Value = System.Math.Clamp(scenery.FogEnd, 50, 2500);
SelectByTag(weatherSeason, scenery.Day.ToString());
SelectByTag(weatherOvercast, scenery.Overcast.ToString(CultureInfo.InvariantCulture));
UpdateWeatherLabels();
}
finally
{
_loadingWeather = false;
}
}
// Reads the form back into the current scenery and flags it for export rewrite.
private void CaptureWeather()
{
if (_loadingWeather || _currentScenery is null)
return;
var scn = _currentScenery;
if (!string.IsNullOrWhiteSpace(weatherTime.Text))
scn.WeatherTime = weatherTime.Text!.Trim();
scn.Day = (int)(weatherDay.Value ?? 0);
scn.Temperature = weatherTemp.Value;
scn.FogEnd = (int)weatherFog.Value;
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.WeatherDirty = true;
UpdateWeatherLabels();
}
private void UpdateWeatherLabels()
{
weatherTempLabel.Text = $"{(int)weatherTemp.Value} °C";
weatherFogLabel.Text = $"{(int)weatherFog.Value} m";
}
private static void SelectByTag(ComboBox combo, string tag)
{
foreach (var obj in combo.Items)
if (obj is ComboBoxItem item && (item.Tag as string) == tag)
{
combo.SelectedItem = item;
return;
}
combo.SelectedItem = null;
}
// --- weather form event handlers ---
private void Weather_OnChanged(object? sender, RoutedEventArgs e) => CaptureWeather();
private void Weather_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) => CaptureWeather();
private void Weather_OnSliderChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e) => CaptureWeather();
private void WeatherDay_OnChanged(object? sender, NumericUpDownValueChangedEventArgs e) => CaptureWeather();
// Picking a season presets the day-of-year, then captures the form.
private void WeatherSeason_OnChanged(object? sender, SelectionChangedEventArgs e)
{
if (_loadingWeather)
return;
if ((weatherSeason.SelectedItem as ComboBoxItem)?.Tag is string tag &&
int.TryParse(tag, out int day))
{
_loadingWeather = true;
weatherDay.Value = day;
_loadingWeather = false;
}
CaptureWeather();
}
// Reads and shows the timetable file referenced by the trainset (its first
// "trainset" token is the timetable name). Shows a note when none is set or
// the file cannot be found.
private void ShowTimetable(Scenery scenery, Trainset trainset)
{
string? path = ResolveTimetablePath(scenery, trainset.Name);
if (path is null)
{
timetableContent.Text = App.Loc["NoTimetable"];
return;
}
try
{
timetableContent.Text = File.ReadAllText(path, Encoding.GetEncoding(1250));
}
catch
{
timetableContent.Text = App.Loc["NoTimetable"];
}
}
// Probes the usual locations for a timetable file named after the trainset.
private static string? ResolveTimetablePath(Scenery scenery, string? name)
{
if (string.IsNullOrWhiteSpace(name) ||
name.Equals("none", System.StringComparison.OrdinalIgnoreCase))
return null;
string scnDir = Path.GetDirectoryName(scenery.Path) ?? "."; // scenery/
string root = Path.GetDirectoryName(scnDir) ?? "."; // MaSzyna root
var candidates = new[]
{
Path.Combine(root, "timetables", name + ".txt"),
Path.Combine(scnDir, name + ".txt"),
Path.Combine(root, "scenario", name + ".txt"),
Path.Combine(root, "timetables", name),
Path.Combine(scnDir, name),
};
return candidates.FirstOrDefault(File.Exists);
}
// Renders the consist preview for a trainset (re-reads its current vehicles
@@ -156,7 +296,7 @@ public partial class Scenarios : UserControl
consistStack.Children.Add(new Image
{
Source = new Bitmap(path),
Height = 45,
Height = 32,
Stretch = Avalonia.Media.Stretch.Uniform,
Margin = new Thickness(0)
});
@@ -171,49 +311,4 @@ public partial class Scenarios : UserControl
if (AppState.Instance.CurrentTrainset is { } trainset)
ShowConsist(trainset);
}
// Exports the (possibly depot-modified) scenery to a $-prefixed copy and
// launches the game on it with the selected consist's driven vehicle.
private void StartButton_OnClick(object? sender, RoutedEventArgs e)
{
var scenery = AppState.Instance.CurrentScenery;
if (scenery is null)
return;
var trainset = AppState.Instance.CurrentTrainset;
// write scenery/$<name>.scn with the replaced trainsets
string dir = System.IO.Path.GetDirectoryName(scenery.Path) ?? "scenery";
string exportName = "$" + System.IO.Path.GetFileName(scenery.Path);
string exportPath = System.IO.Path.Combine(dir, exportName);
try
{
File.WriteAllText(exportPath, scenery.BuildExportContent(), Encoding.GetEncoding(1250));
}
catch
{
return; // couldn't write the scenery file
}
// the player's vehicle is the node name of the driven car in the consist
string? vehicle = trainset?.Vehicles
.FirstOrDefault(v => v.DriverType is eDriverType.Headdriver or eDriverType.Reardriver)?.Name
?? trainset?.Vehicles.FirstOrDefault()?.Name;
// make sure the latest settings are on disk before the sim reads them
StarterNG.Classes.Settings.Instance.CaptureAndSave();
// launch the game: -s $<name>.scn -v <vehicle>
try
{
Process.Start(new ProcessStartInfo(StarterNG.Classes.Settings.Instance.ExecutablePath)
{
Arguments = $"-s {exportName} -v {vehicle}",
UseShellExecute = true
});
}
catch
{
// executable not found / not configured yet
}
}
}

View File

@@ -2,15 +2,14 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:objectModel="clr-namespace:System.Collections.ObjectModel;assembly=System.ObjectModel"
xmlns:starterNg="clr-namespace:StarterNG"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="StarterNG.Views.Settings"
x:CompileBindings="True">
<DockPanel>
<!-- Save / reset bar -->
<Border DockPanel.Dock="Bottom" Padding="16,10" Background="#11000000">
<Border DockPanel.Dock="Bottom" Padding="16,10" Background="{StaticResource CarbonPanelBg}"
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="0,1,0,0">
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Right">
<TextBlock x:Name="SaveStatus" VerticalAlignment="Center" Opacity="0.8" Text="" />
<Button x:Name="ResetButton" Classes="Flat" Click="ResetButton_OnClick"
@@ -20,93 +19,89 @@
</StackPanel>
</Border>
<suki:SettingsLayout>
<suki:SettingsLayout.Items>
<objectModel:ObservableCollection x:TypeArguments="suki:SettingsLayoutItem">
<suki:SettingsLayoutItem Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
<suki:SettingsLayoutItem.Content>
<StackPanel Spacing="8" Orientation="Vertical">
<TabControl TabStripPlacement="Left">
<!-- General -->
<TabItem Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
<StackPanel Spacing="8" Orientation="Horizontal">
<Label Content="{Binding [Language], Source={x:Static starterNg:App.Loc}}" VerticalAlignment="Center" FontSize="14"/>
<Label Content="{Binding [Language], Source={x:Static starterNg:App.Loc}}" VerticalAlignment="Center" FontSize="14" />
<ComboBox x:Name="ChangeLanguageCb" MinWidth="350" MaxWidth="350" SelectedIndex="1" SelectionChanged="LanguageComboBox_OnSelectionChanged">
<ComboBoxItem>Polski</ComboBoxItem>
<ComboBoxItem>English</ComboBoxItem>
</ComboBox>
</StackPanel>
<CheckBox x:Name="FullscreenCb" Content="{Binding [Fullscreen], Source={x:Static starterNg:App.Loc}}"/>
<CheckBox x:Name="FullscreenCb" Content="{Binding [Fullscreen], Source={x:Static starterNg:App.Loc}}" />
<CheckBox x:Name="PauseInactiveCb" Content="{Binding [PauseSimInactive], Source={x:Static starterNg:App.Loc}}" />
<CheckBox x:Name="PauseStartCb" Content="{Binding [PauseSimStart], Source={x:Static starterNg:App.Loc}}" />
<StackPanel Spacing="8" Orientation="Horizontal">
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [CursorSensitivity], Source={x:Static starterNg:App.Loc}}"/>
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [CursorSensitivity], Source={x:Static starterNg:App.Loc}}" />
<Slider x:Name="CursorSensitivitySlider" Minimum="1" Maximum="5" TickFrequency="1" MinWidth="200" MaxWidth="200"
IsSnapToTickEnabled="True" Value="3">
</Slider>
IsSnapToTickEnabled="True" Value="3" />
</StackPanel>
<CheckBox x:Name="MouseHorInvertCb" Content="{Binding [MouseHorInvert], Source={x:Static starterNg:App.Loc}}"/>
<CheckBox x:Name="MouseHorInvertCb" Content="{Binding [MouseHorInvert], Source={x:Static starterNg:App.Loc}}" />
<CheckBox x:Name="MouseVertInvertCb" Content="{Binding [MouseVertInvert], Source={x:Static starterNg:App.Loc}}" />
</StackPanel>
</suki:SettingsLayoutItem.Content>
</suki:SettingsLayoutItem>
<suki:SettingsLayoutItem Header="{Binding [Communication], Source={x:Static starterNg:App.Loc}}">
<suki:SettingsLayoutItem.Content>
<StackPanel Spacing="8" Orientation="Vertical">
<CheckBox x:Name="GamepadIgnoreCb" Content="{Binding [GamepadSignalIgnore], Source={x:Static starterNg:App.Loc}}"/>
</ScrollViewer>
</TabItem>
<!-- Communication -->
<TabItem Header="{Binding [Communication], 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}}"/>
<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}}"/>
<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>
</suki:SettingsLayoutItem.Content>
</suki:SettingsLayoutItem>
</ScrollViewer>
</TabItem>
<suki:SettingsLayoutItem Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
<suki:SettingsLayoutItem.Content>
<StackPanel Spacing="8" Orientation="Vertical">
<!-- 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}}"/>
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}" />
<ComboBox x:Name="SelectExeCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
<ComboBoxItem Content="{Binding [SelectEXEAuto], Source={x:Static starterNg:App.Loc}}"/>
<ComboBoxItem Content="{Binding [SelectEXEAuto], Source={x:Static starterNg:App.Loc}}" />
<ComboBoxItem>eu07.exe</ComboBoxItem>
</ComboBox>
</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="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}" />
<CheckBox x:Name="VirtualShuntingCb" Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}" />
</StackPanel>
</suki:SettingsLayoutItem.Content>
</suki:SettingsLayoutItem>
</ScrollViewer>
</TabItem>
<suki:SettingsLayoutItem Header="{Binding [Graphic], Source={x:Static starterNg:App.Loc}}">
<suki:SettingsLayoutItem.Content>
<StackPanel Spacing="8" Orientation="Vertical">
<!-- Graphic -->
<TabItem Header="{Binding [Graphic], 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 [RenderEngine], Source={x:Static starterNg:App.Loc}}"/>
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [RenderEngine], Source={x:Static starterNg:App.Loc}}" />
<ComboBox x:Name="RenderEngineCb" MinWidth="150" MaxWidth="150" 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}}"/>
<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>
<StackPanel Spacing="8" Orientation="Horizontal">
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Resolution], Source={x:Static starterNg:App.Loc}}"/>
<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>
@@ -117,283 +112,188 @@
</ComboBox>
</StackPanel>
<StackPanel Spacing="8" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BufforResolution], Source={x:Static starterNg:App.Loc}}"/>
<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}%'}" />
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 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 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" />
<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" />
<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 [RenderRange], Source={x:Static starterNg:App.Loc}}"/>
<Slider x:Name="RenderRangeSlider" Minimum="1" MaxWidth="400"
HorizontalAlignment="Left"
<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>
<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}}"/>
<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" />
<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>
<StackPanel Spacing="8" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Postprocessing], Source={x:Static starterNg:App.Loc}}"/>
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Postprocessing], Source={x:Static starterNg:App.Loc}}" />
<ComboBox x:Name="PostprocessingCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
<ComboBoxItem Content="{Binding [PPReinhard], Source={x:Static starterNg:App.Loc}}"/>
<ComboBoxItem Content="{Binding [PPACES], Source={x:Static starterNg:App.Loc}}"/>
<ComboBoxItem Content="{Binding [PPReinhard], Source={x:Static starterNg:App.Loc}}" />
<ComboBoxItem Content="{Binding [PPACES], Source={x:Static starterNg:App.Loc}}" />
</ComboBox>
</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}}"/>
<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'}" />
<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 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'}" />
<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'}" />
<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}}"/>
<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}}"/>
<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}}"/>
<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}}"/>
<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 [ShadowsCabRange], 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} stopni'}" />
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowsCabRange], 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} stopni'}" />
</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}}"/>
<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 Spacing="8" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderScreensFramerate], Source={x:Static starterNg:App.Loc}}"/>
<Slider x:Name="RenderScreensFramerateSlider" Minimum="1" MaxWidth="400"
HorizontalAlignment="Left"
MinWidth="250" Maximum="5"
TickFrequency="1"
IsSnapToTickEnabled="True" Value="5" />
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderScreensFramerate], Source={x:Static starterNg:App.Loc}}" />
<Slider x:Name="RenderScreensFramerateSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
MinWidth="250" Maximum="5" TickFrequency="1" IsSnapToTickEnabled="True" Value="5" />
</StackPanel>
</StackPanel>
</suki:SettingsLayoutItem.Content>
</suki:SettingsLayoutItem>
</ScrollViewer>
</TabItem>
<suki:SettingsLayoutItem Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
<suki:SettingsLayoutItem.Content>
<StackPanel Spacing="8" Orientation="Vertical">
<!-- Physics -->
<TabItem Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
<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" />
<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>
<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="CrashDamageCb" Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}"/>
<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="CrashDamageCb" Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}" />
</StackPanel>
</suki:SettingsLayoutItem.Content>
</suki:SettingsLayoutItem>
<suki:SettingsLayoutItem Header="{Binding [Sounds], Source={x:Static starterNg:App.Loc}}">
<suki:SettingsLayoutItem.Content>
<StackPanel Spacing="8" Orientation="Vertical">
<CheckBox x:Name="EnableSoundsCb" Content="{Binding [EnableSounds], Source={x:Static starterNg:App.Loc}}"/>
</ScrollViewer>
</TabItem>
<!-- Sounds -->
<TabItem Header="{Binding [Sounds], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
<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" />
<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" />
<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" />
<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" />
<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" />
<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 Spacing="8" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VolumeDuringPause], Source={x:Static starterNg:App.Loc}}"/>
<Slider x:Name="PauseVolumeSlider" Minimum="0" MaxWidth="400"
HorizontalAlignment="Left"
MinWidth="250" Maximum="100"
TickFrequency="5"
IsSnapToTickEnabled="True" Value="0" />
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VolumeDuringPause], Source={x:Static starterNg:App.Loc}}" />
<Slider x:Name="PauseVolumeSlider" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="0" />
</StackPanel>
</StackPanel>
</suki:SettingsLayoutItem.Content>
</suki:SettingsLayoutItem>
</ScrollViewer>
</TabItem>
<suki:SettingsLayoutItem Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
<suki:SettingsLayoutItem.Content>
<StackPanel Spacing="8" Orientation="Vertical">
<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}}"/>
<!-- Starter -->
<TabItem Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
<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>
</suki:SettingsLayoutItem.Content>
</suki:SettingsLayoutItem>
</objectModel:ObservableCollection>
</suki:SettingsLayout.Items>
</suki:SettingsLayout>
</ScrollViewer>
</TabItem>
</TabControl>
</DockPanel>
</UserControl>

View File

@@ -8,12 +8,40 @@
<String key="NavDepot">Depot</String>
<String key="NavSettings">Settings</String>
<String key="NavControls">Controls</String>
<String key="Help">Help</String>
<String key="Version">Version:</String>
<!-- General view -->
<String key="Sceneries">Sceneries</String>
<String key="Vehicles">Vehicles</String>
<String key="ScenarioDescription">Scenario description</String>
<String key="SceneryDescription">Scenery description</String>
<String key="TabDescription">Description</String>
<String key="Weather">Weather</String>
<String key="Timetable">Timetable</String>
<String key="NoWeatherData">No weather data in the scenery.</String>
<String key="NoTimetable">No timetable for the selected consist.</String>
<String key="Time">Time</String>
<String key="Day">Day of year</String>
<String key="Sky">Sky</String>
<String key="Fog">Fog / visibility</String>
<String key="Season">Season</String>
<String key="Temperature">Temperature</String>
<String key="Visibility">Visibility (fog)</String>
<String key="Overcast">Overcast</String>
<String key="SeasonNow">Today</String>
<String key="SeasonSpring">Spring</String>
<String key="SeasonSummer">Summer</String>
<String key="SeasonAutumn">Autumn</String>
<String key="SeasonWinter">Winter</String>
<String key="OvercastClear">Clear</String>
<String key="OvercastFair">Fair</String>
<String key="OvercastLight">Light clouds</String>
<String key="OvercastModerate">Moderate clouds</String>
<String key="OvercastHeavy">Heavy clouds</String>
<String key="OvercastCloudy">Cloudy</String>
<String key="OvercastStorm">Storm</String>
<String key="WeatherHint">Weather changes are written to the scenery on launch.</String>
<String key="Consist">Consist</String>
<String key="Start">Start</String>
<String key="ShowLatestLogTooltip">Open latest simulation log</String>
@@ -57,7 +85,32 @@
<String key="CatTruck">Truck</String>
<String key="CatPeople">People</String>
<String key="CatAnimals">Animals</String>
<String key="CatWagons">Wagons</String>
<String key="CatWagonsA">Wagons A</String>
<String key="CatWagonsB">Wagons B</String>
<String key="CatWagonsC">Wagons C</String>
<String key="CatWagonsD">Wagons D</String>
<String key="CatWagonsE">Wagons E</String>
<String key="CatWagonsF">Wagons F</String>
<String key="CatWagonsG">Wagons G</String>
<String key="CatWagonsH">Wagons H</String>
<String key="CatWagonsI">Wagons I</String>
<String key="CatWagonsJ">Wagons J</String>
<String key="CatWagonsK">Wagons K</String>
<String key="CatWagonsL">Wagons L</String>
<String key="CatWagonsM">Wagons M</String>
<String key="CatWagonsN">Wagons N</String>
<String key="CatWagonsO">Wagons O</String>
<String key="CatWagonsP">Wagons P</String>
<String key="CatWagonsQ">Wagons Q</String>
<String key="CatWagonsR">Wagons R</String>
<String key="CatWagonsS">Wagons S</String>
<String key="CatWagonsT">Wagons T</String>
<String key="CatWagonsU">Wagons U</String>
<String key="CatWagonsV">Wagons V</String>
<String key="CatWagonsW">Wagons W</String>
<String key="CatWagonsX">Wagons X</String>
<String key="CatWagonsY">Wagons Y</String>
<String key="CatWagonsZ">Wagons Z</String>
<String key="CatOther">Other</String>
<String key="Brakes">Brakes</String>
<String key="Loads">Loads</String>

View File

@@ -8,12 +8,40 @@
<String key="NavDepot">Zajezdnia</String>
<String key="NavSettings">Ustawienia</String>
<String key="NavControls">Sterowanie</String>
<String key="Help">Pomoc</String>
<String key="Version">Wersja:</String>
<!-- Widok ogolny -->
<String key="Sceneries">Scenerie</String>
<String key="Vehicles">Pojazdy</String>
<String key="ScenarioDescription">Opis scenariusza</String>
<String key="SceneryDescription">Opis scenerii</String>
<String key="TabDescription">Opis</String>
<String key="Weather">Pogoda</String>
<String key="Timetable">Rozkład jazdy</String>
<String key="NoWeatherData">Brak danych o pogodzie w scenerii.</String>
<String key="NoTimetable">Brak rozkładu jazdy dla wybranego składu.</String>
<String key="Time">Godzina</String>
<String key="Day">Dzień roku</String>
<String key="Sky">Niebo</String>
<String key="Fog">Mgła / widoczność</String>
<String key="Season">Pora roku</String>
<String key="Temperature">Temperatura</String>
<String key="Visibility">Widoczność (mgła)</String>
<String key="Overcast">Zachmurzenie</String>
<String key="SeasonNow">Dziś</String>
<String key="SeasonSpring">Wiosna</String>
<String key="SeasonSummer">Lato</String>
<String key="SeasonAutumn">Jesień</String>
<String key="SeasonWinter">Zima</String>
<String key="OvercastClear">Bezchmurnie</String>
<String key="OvercastFair">Pogodnie</String>
<String key="OvercastLight">Lekkie zachmurzenie</String>
<String key="OvercastModerate">Umiarkowane zachmurzenie</String>
<String key="OvercastHeavy">Duże zachmurzenie</String>
<String key="OvercastCloudy">Pochmurno</String>
<String key="OvercastStorm">Burza</String>
<String key="WeatherHint">Zmiany pogody zostaną zapisane do scenerii przy uruchomieniu.</String>
<String key="Consist">Zestawienie</String>
<String key="Start">Uruchom</String>
<String key="ShowLatestLogTooltip">Otwórz ostatni log symulatora</String>
@@ -57,7 +85,32 @@
<String key="CatTruck">Samochód ciężarowy</String>
<String key="CatPeople">Ludzie</String>
<String key="CatAnimals">Zwierzęta</String>
<String key="CatWagons">Wagony</String>
<String key="CatWagonsA">Wagony A</String>
<String key="CatWagonsB">Wagony B</String>
<String key="CatWagonsC">Wagony C</String>
<String key="CatWagonsD">Wagony D</String>
<String key="CatWagonsE">Wagony E</String>
<String key="CatWagonsF">Wagony F</String>
<String key="CatWagonsG">Wagony G</String>
<String key="CatWagonsH">Wagony H</String>
<String key="CatWagonsI">Wagony I</String>
<String key="CatWagonsJ">Wagony J</String>
<String key="CatWagonsK">Wagony K</String>
<String key="CatWagonsL">Wagony L</String>
<String key="CatWagonsM">Wagony M</String>
<String key="CatWagonsN">Wagony N</String>
<String key="CatWagonsO">Wagony O</String>
<String key="CatWagonsP">Wagony P</String>
<String key="CatWagonsQ">Wagony Q</String>
<String key="CatWagonsR">Wagony R</String>
<String key="CatWagonsS">Wagony S</String>
<String key="CatWagonsT">Wagony T</String>
<String key="CatWagonsU">Wagony U</String>
<String key="CatWagonsV">Wagony V</String>
<String key="CatWagonsW">Wagony W</String>
<String key="CatWagonsX">Wagony X</String>
<String key="CatWagonsY">Wagony Y</String>
<String key="CatWagonsZ">Wagony Z</String>
<String key="CatOther">Inne</String>
<String key="Brakes">Hamulce</String>
<String key="Loads">Ładunki</String>