Fix the language picker and tidy the graphics tab

Both language combos decided a change was the user's by checking
IsKeyboardFocusWithin on the picked item. Selecting with the mouse leaves the
focus on the combo, not on the item, so on Windows the check failed and the
language never changed. The handlers now compare the picked code with the one
already loaded instead of guessing who caused the event: filling the combo in
selects what is loaded and stops there, a real pick differs and applies.

The settings tab listed a hard-coded Polski / English pair and wrote the
language back as "Polski" or "English", so picking Czech, Hungarian or
Russian in the top bar was overwritten on save. It is filled from lang/ now,
and writes back the selected entry.

LocalizationService gains a LanguageChanged event: the main window rebuilds
the captions that code builds once (train stats, panel headers, combo items)
from there, so a pick in either place refreshes them. Its PropertyChanged
also names the indexer "Item[]", which is what bindings listen for.

Graphics tab: the track-curve quality slider moves over from Physics, the
view angle shows a degree sign instead of the Polish "stopni", and the HDR
tone-mapping picker is gone - only Reinhard.glsl ever shipped, so
ApplyHdrShader copies that one and the starter.hdr setting is dropped.
This commit is contained in:
maj00r
2026-08-25 22:14:13 +02:00
parent 4d8976e43d
commit e3caa9d38f
10 changed files with 107 additions and 70 deletions

View File

@@ -26,10 +26,9 @@
<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" />
<ComboBox x:Name="ChangeLanguageCb" MinWidth="350" MaxWidth="350" SelectedIndex="1" SelectionChanged="LanguageComboBox_OnSelectionChanged">
<ComboBoxItem>Polski</ComboBoxItem>
<ComboBoxItem>English</ComboBoxItem>
</ComboBox>
<!-- Filled from the lang/ folder in the constructor. -->
<ComboBox x:Name="ChangeLanguageCb" MinWidth="350" MaxWidth="350"
SelectionChanged="LanguageComboBox_OnSelectionChanged" />
</StackPanel>
<CheckBox x:Name="FullscreenCb" Content="{Binding [Fullscreen], Source={x:Static starterNg:App.Loc}}" />
@@ -130,13 +129,6 @@
<ComboBoxItem Content="{Binding [REExperimental], Source={x:Static starterNg:App.Loc}}" />
</ComboBox>
</StackPanel>
<StackPanel Spacing="8" Orientation="Horizontal">
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [HdrToneMap], Source={x:Static starterNg:App.Loc}}" />
<ComboBox x:Name="HdrToneMapCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
<ComboBoxItem Content="Reinhard" />
<ComboBoxItem Content="ACES Film" />
</ComboBox>
</StackPanel>
<StackPanel Spacing="8" Orientation="Horizontal">
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Resolution], Source={x:Static starterNg:App.Loc}}" />
<ComboBox x:Name="ResolutionCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
@@ -188,6 +180,11 @@
<Slider x:Name="RenderRangeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
MinWidth="250" Maximum="3" Value="1" />
</StackPanel>
<StackPanel Spacing="8" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrackCurvesQuality], Source={x:Static starterNg:App.Loc}}" />
<Slider x:Name="TrackCurvesSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
MinWidth="250" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" />
</StackPanel>
<StackPanel Spacing="8" Orientation="Horizontal">
<CheckBox x:Name="FpsLimitEnableCb" Content="{Binding [FpsLimitEnable], Source={x:Static starterNg:App.Loc}}" />
<Slider x:Name="FpsLimitSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
@@ -258,7 +255,7 @@
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [FieldOfView], Source={x:Static starterNg:App.Loc}}" />
<Slider Name="fovSlider" Minimum="5" MaxWidth="400" HorizontalAlignment="Left"
MinWidth="250" Maximum="110" TickFrequency="5" IsSnapToTickEnabled="True" Value="30" />
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #fovSlider.Value, StringFormat='{}{0} stopni'}" />
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #fovSlider.Value, StringFormat='{}{0}°'}" />
</StackPanel>
<CheckBox x:Name="RenderScreensCb" Content="{Binding [RenderScreens], Source={x:Static starterNg:App.Loc}}" />
<CheckBox x:Name="RenderScreensThreadCb" Content="{Binding [RenderScreensThread], Source={x:Static starterNg:App.Loc}}" />
@@ -270,11 +267,6 @@
<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" />
</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}}" />

View File

@@ -12,6 +12,7 @@ using Avalonia.Layout;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Media;
using StarterNG.Classes;
using StarterNG.Services;
// Disambiguate from Avalonia.Input.KeyBinding (a command-input binding) imported above.
using KeyBinding = StarterNG.Classes.KeyBinding;
@@ -34,11 +35,10 @@ public partial class Settings : UserControl
shaderResolutionSlider_OnValueChanged(null, null);
};
ChangeLanguageCb.SelectedIndex = App.Loc.CurrentLanguage switch
{
"Polski" => 0,
_ => 1
};
FillLanguageCombo();
// The other picker is in the main window's top bar; both follow whatever
// language ends up loaded.
App.Loc.LanguageChanged += SelectActiveLanguage;
FeedbackCb.SelectionChanged += FeedbackCb_OnSelectionChanged;
FpsLimitEnableCb.IsCheckedChanged += (_, _) => FpsLimitSlider.IsEnabled = IsChecked(FpsLimitEnableCb);
@@ -143,7 +143,7 @@ public partial class Settings : UserControl
PopulateExeCombo();
// General
ChangeLanguageCb.SelectedIndex = s.Language == "Polski" ? 0 : 1;
SelectActiveLanguage();
FullscreenCb.IsChecked = s.Fullscreen;
PauseInactiveCb.IsChecked = s.PauseWhenInactive;
PauseStartCb.IsChecked = s.PauseOnStart;
@@ -177,7 +177,6 @@ public partial class Settings : UserControl
// Graphics
RenderEngineCb.SelectedIndex = s.RenderEngine;
HdrToneMapCb.SelectedIndex = Clamp(s.HdrToneMap, 0, 1);
SelectResolution(s.Width, s.Height);
bufferScale.Value = s.BufferScalePercent;
textureResolutionSlider.Value = Log2(s.MaxTextureSize, 12);
@@ -264,7 +263,10 @@ public partial class Settings : UserControl
var s = StarterNG.Classes.Settings.Instance;
// General
s.Language = ChangeLanguageCb.SelectedIndex == 0 ? "Polski" : "English";
// Keep the loaded language rather than writing a wrong one over it when
// the combo has no valid pick.
s.Language = (ChangeLanguageCb.SelectedItem as ComboBoxItem)?.Content?.ToString()
?? App.Loc.CurrentLanguage;
s.Fullscreen = IsChecked(FullscreenCb);
s.PauseWhenInactive = IsChecked(PauseInactiveCb);
s.PauseOnStart = IsChecked(PauseStartCb);
@@ -297,7 +299,6 @@ public partial class Settings : UserControl
// Graphics
s.RenderEngine = Math.Max(0, RenderEngineCb.SelectedIndex);
s.HdrToneMap = Clamp(HdrToneMapCb.SelectedIndex, 0, 1);
ReadResolution(s);
s.BufferScalePercent = (int)bufferScale.Value;
s.MaxTextureSize = 1 << (int)textureResolutionSlider.Value;
@@ -454,14 +455,39 @@ public partial class Settings : UserControl
shaderResolution.Text = $"{resolution} px";
}
// One entry per file in the lang/ folder, so every translation is reachable
// (the list used to be a hard-coded Polski / English pair).
private void FillLanguageCombo()
{
ChangeLanguageCb.Items.Clear();
foreach (var lang in LocalizationService.AvailableLanguages())
ChangeLanguageCb.Items.Add(new ComboBoxItem { Content = lang.Name, Tag = lang.Code });
if (ChangeLanguageCb.ItemCount == 0)
ChangeLanguageCb.Items.Add(new ComboBoxItem { Content = "English", Tag = "en" });
SelectActiveLanguage();
}
// Points the combo at the language currently loaded. The selection change
// lands in the handler below, which sees it is already loaded and stops.
private void SelectActiveLanguage()
{
var item = ChangeLanguageCb.Items.OfType<ComboBoxItem>()
.FirstOrDefault(it => string.Equals(it.Tag as string, App.Loc.CurrentLangCode,
StringComparison.OrdinalIgnoreCase))
?? ChangeLanguageCb.Items.OfType<ComboBoxItem>().FirstOrDefault();
if (item != null && !ReferenceEquals(ChangeLanguageCb.SelectedItem, item))
ChangeLanguageCb.SelectedItem = item;
}
private void LanguageComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (sender is not ComboBox { SelectedItem: ComboBoxItem item }) return;
if (!item.IsKeyboardFocusWithin) return; // ignore programmatic (ApplyToUi) changes
var lang = item.Content?.ToString();
if (string.IsNullOrEmpty(lang)) return;
if ((ChangeLanguageCb.SelectedItem as ComboBoxItem)?.Tag is not string code)
return;
if (string.Equals(code, App.Loc.CurrentLangCode, StringComparison.OrdinalIgnoreCase))
return;
App.ApplyLanguage(lang);
App.ApplyLanguage(code);
}
private void FeedbackCb_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) =>